JVM源碼分析之Java對象頭實現


 

 

原創申明:本文由公眾號【猿燈塔】原創,轉載請說明出處標注

 

“365篇原創計划”第十一篇。

 

今天呢!燈塔君跟大家講:

 

JVM源碼分析之Java對象頭實現

 

HotSpot虛擬機中,對象在內存中的布局分為三塊區域:對象頭、實例數據和對齊填充。

對象頭

對象頭包括兩部分:Mark Word 和 類型指針。

 

Mark Word

 

Mark Word用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,占用內存大小與虛擬機位長一致。

 

類型指針

類型指針指向對象的類元數據,虛擬機通過這個指針確定該對象是哪個類的實例。

markOop實現

HotSpot通過markOop類型實現Mark Word,具體實現位於markOop.hpp文件中。

由於對象需要存儲的運行時數據很多,考慮到虛擬機的內存使用,markOop被設計成一個非固定的數據結構,以便在極小的空間存儲盡量多的數據,根據對象的狀態復用自己的存儲空間,32位虛擬機的markOop實現如下:

 

 

hash: 保存對象的哈希碼

age: 保存對象的分代年齡

biased_lock: 偏向鎖標識位

lock: 鎖狀態標識位

JavaThread:* 保存持有偏向鎖的線程ID

epoch: 保存偏向時間戳

markOop:中不同的鎖標識位,代表着不同的鎖狀態:

不同的鎖狀態,存儲着不同的數據:

 

 

markOop中提供了大量方法用於查看當前對象頭的狀態,以及更新對象頭的數據,為synchronized鎖的實現提供了基礎。

 

下面來看看代碼吧:

首先定義兩個簡單的類AAA和BBB

通過``javap -c AAA```查看編譯之后的字節碼,具體如下:

 

Java中的new關鍵字對應jvm中的new指令,定義在InterpreterRuntime類中,實現如下:

new指令的實現過程:

1、其中pool是AAA的constant pool,此時AAA的class已經加載到虛擬機中,new指令后面的#2表示BBB類全限定名的符號引用在constant pool的位置;

2、方法pool->klass_at負責返回BBB對應的klassOop對象,實現如下:

如果常量池中指定位置(#2)的數據已經是個oop類型,說明BBB的class已經被加載並解析過,則直接通過(klassOop)entry.get_oop()返回klassOop;否則表示第一次使用BBB,需要解析BBB的符號引用,並加載BBB的class類,生成對應的instanceKlass對象,並更新constant pool中對應位置的符號引用;

 

3、klass->check_valid_for_instantiation可以防止抽象類被實例化;

4、klass->initialize實現如下:

如果BBB的instanceKlass對象已經初始化完成,則直接返回;否則通過initialize_impl方法進行初始化,整個初始化算法分成11步,具體實現如下:

step1

通過ObjectLocker在初始化之前進行加鎖,防止多個線程並發初始化。

 

step2

step3

如果當前instanceKlass處於being_initialized狀態,且被當前線程初始化,則直接返回。

其實對於這個step的處理我有疑問,什么情況會走到這一步?經過RednaxelaFX大大提點,如下情況會執行step3:

例如A類有靜態變量指向一個new B類實例,B類里又有靜態變量指向new A類實例,這樣外部用A時要初始化A類,初始化過程中又要觸發B類初始化,B類初始化又再次觸發A類初始化。

 

 

 

 

如果當前instanceKlass處於fully_initialized狀態,說明已經初始化完成,則直接返回;

 

step5

 

如果當前instanceKlass處於initialization_error狀態,說明初始化失敗了,拋出異常。

 

step6

 

設置當前instanceKlass的狀態為 being_initialized;設置初始化線程為當前線程。

 

如果當前instanceKlass不是接口類型,並且父類不為空,且還未初始化,則執行父類的初始化。

 

step8

 

通過thisoop->callclass_initializer方法執行靜態塊代碼,實現如下:

 

this_oop->class_initializer()可以獲取靜態代碼塊入口,最終通過JavaCalls::call執行代碼塊邏輯,再下一層就是具體操作系統的實現了。

 

step9

 

如果初始化過程沒有異常,說明instanceKlass對象已經初始完成,則設置當前instanceKlass的狀態為 fully_initialized,最后通知其它線程初始化已經完成;否則執行step10 and 11。

 

step10 and 11

 

如果初始化發生異常,則設置當前instanceKlass的狀態為 initialization_error,並通知其它線程初始化發生異常。

 

5、如果instanceKlass初始化完成,klass->allocate_instance會在堆內存創建instanceOopDesc對象,即類的實例化;.

instanceOopDesc

當在Java中new一個對象時,本質是在堆內存創建一個instanceOopDesc對象。

 

instanceOopDesc在實現上繼承自oopDesc,其中oopDesc定義如下:

當然,這只是 oopDesc的部分實現,oopDesc包含兩個數據成員:_mark 和 _metadata。

1、_mark是markOop類型對象,用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,占用內存大小與虛擬機位長一致,更具體的實現可以閱讀 《java對象頭的HotSpot實現分析》

2、_metadata是一個聯合體,其中wideKlassOop和narrowOop都是指向InstanceKlass對象的指針,wide版是普通指針,narrow版是壓縮類指針(compressed Class pointer)

 

instanceOopDesc對象的創建過程

instanceOopDesc對象通過instanceKlass::allocate_instance進行創建,實現過程如下:

1、has_finalizer判斷當前類是否包含不為空的finalize方法;

2、size_helper確定創建當前對象需要分配多大內存;

3、CollectedHeap::obj_allocate從堆中申請指定大小的內存,並創建instanceOopDesc對象,實現如下:

 

 

4、如果當前類重寫了finalize方法,且非空,需要把生成的對象封裝成Finalizer對象並添加到 Finalizer鏈表中,對象被GC時,如果是Finalizer對象,會將對象賦值到pending對象。Reference Handler線程會將pending對象push到queue中,Finalizer線程poll到對象,先刪除掉Finalizer鏈表中對應的對象,然后再執行對象的finalize方法;

365天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回復【資料】【面試】【簡歷】有我准備的一線大廠面試資料和簡歷模板

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM