java中Mark接口_JVM源码分析之Java对象头实现
原標題:JVM源碼分析之Java對象頭實現(xiàn)
原創(chuàng)申明:本文由公眾號【猿燈塔】原創(chuàng),轉(zhuǎn)載請說明出處標注
“365篇原創(chuàng)計劃”第十一篇。
今天呢!燈塔君跟大家講:
JVM源碼分析之Java對象頭實現(xiàn)
HotSpot虛擬機中,對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭、實例數(shù)據(jù)和對齊填充。
對象頭
對象頭包括兩部分:Mark Word 和 類型指針。
Mark Word
Mark Word用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,占用內(nèi)存大小與虛擬機位長一致。
類型指針
類型指針指向?qū)ο蟮念愒獢?shù)據(jù),虛擬機通過這個指針確定該對象是哪個類的實例。
markOop實現(xiàn)
HotSpot通過markOop類型實現(xiàn)Mark Word,具體實現(xiàn)位于markOop.hpp文件中。
由于對象需要存儲的運行時數(shù)據(jù)很多,考慮到虛擬機的內(nèi)存使用,markOop被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu),以便在極小的空間存儲盡量多的數(shù)據(jù),根據(jù)對象的狀態(tài)復(fù)用自己的存儲空間,32位虛擬機的markOop實現(xiàn)如下:
hash: 保存對象的哈希碼
age: 保存對象的分代年齡
biased_lock: 偏向鎖標識位
lock: 鎖狀態(tài)標識位
JavaThread:* 保存持有偏向鎖的線程ID
epoch: 保存偏向時間戳
markOop:中不同的鎖標識位,代表著不同的鎖狀態(tài):
不同的鎖狀態(tài),存儲著不同的數(shù)據(jù):
markOop中提供了大量方法用于查看當(dāng)前對象頭的狀態(tài),以及更新對象頭的數(shù)據(jù),為synchronized鎖的實現(xiàn)提供了基礎(chǔ)。
下面來看看代碼吧:
首先定義兩個簡單的類AAA和BBB
通過``javap -c AAA```查看編譯之后的字節(jié)碼,具體如下:
Java中的new關(guān)鍵字對應(yīng)jvm中的new指令,定義在InterpreterRuntime類中,實現(xiàn)如下:
new指令的實現(xiàn)過程:
1、其中pool是AAA的constant pool,此時AAA的class已經(jīng)加載到虛擬機中,new指令后面的#2表示BBB類全限定名的符號引用在constant pool的位置;
2、方法pool->klass_at負責(zé)返回BBB對應(yīng)的klassOop對象,實現(xiàn)如下:
如果常量池中指定位置(#2)的數(shù)據(jù)已經(jīng)是個oop類型,說明BBB的class已經(jīng)被加載并解析過,則直接通過(klassOop)entry.get_oop()返回klassOop;否則表示第一次使用BBB,需要解析BBB的符號引用,并加載BBB的class類,生成對應(yīng)的instanceKlass對象,并更新constant pool中對應(yīng)位置的符號引用;
3、klass->check_valid_for_instantiation可以防止抽象類被實例化;
4、klass->initialize實現(xiàn)如下:
如果BBB的instanceKlass對象已經(jīng)初始化完成,則直接返回;否則通過initialize_impl方法進行初始化,整個初始化算法分成11步,具體實現(xiàn)如下:
step1
通過ObjectLocker在初始化之前進行加鎖,防止多個線程并發(fā)初始化。
step2
step3
如果當(dāng)前instanceKlass處于being_initialized狀態(tài),且被當(dāng)前線程初始化,則直接返回。
其實對于這個step的處理我有疑問,什么情況會走到這一步?經(jīng)過RednaxelaFX大大提點,如下情況會執(zhí)行step3:
例如A類有靜態(tài)變量指向一個new B類實例,B類里又有靜態(tài)變量指向new A類實例,這樣外部用A時要初始化A類,初始化過程中又要觸發(fā)B類初始化,B類初始化又再次觸發(fā)A類初始化。
如果當(dāng)前instanceKlass處于fully_initialized狀態(tài),說明已經(jīng)初始化完成,則直接返回;
step5
如果當(dāng)前instanceKlass處于initialization_error狀態(tài),說明初始化失敗了,拋出異常。
step6
設(shè)置當(dāng)前instanceKlass的狀態(tài)為 being_initialized;設(shè)置初始化線程為當(dāng)前線程。
如果當(dāng)前instanceKlass不是接口類型,并且父類不為空,且還未初始化,則執(zhí)行父類的初始化。
step8
通過thisoop->callclass_initializer方法執(zhí)行靜態(tài)塊代碼,實現(xiàn)如下:
this_oop->class_initializer()可以獲取靜態(tài)代碼塊入口,最終通過JavaCalls::call執(zhí)行代碼塊邏輯,再下一層就是具體操作系統(tǒng)的實現(xiàn)了。
step9
如果初始化過程沒有異常,說明instanceKlass對象已經(jīng)初始完成,則設(shè)置當(dāng)前instanceKlass的狀態(tài)為 fully_initialized,最后通知其它線程初始化已經(jīng)完成;否則執(zhí)行step10 and 11。
step10 and 11
如果初始化發(fā)生異常,則設(shè)置當(dāng)前instanceKlass的狀態(tài)為 initialization_error,并通知其它線程初始化發(fā)生異常。
5、如果instanceKlass初始化完成,klass->allocate_instance會在堆內(nèi)存創(chuàng)建instanceOopDesc對象,即類的實例化;.
instanceOopDesc
當(dāng)在Java中new一個對象時,本質(zhì)是在堆內(nèi)存創(chuàng)建一個instanceOopDesc對象。
instanceOopDesc在實現(xiàn)上繼承自oopDesc,其中oopDesc定義如下:
當(dāng)然,這只是 oopDesc的部分實現(xiàn),oopDesc包含兩個數(shù)據(jù)成員:_mark 和 _metadata。
1、_mark是markOop類型對象,用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,占用內(nèi)存大小與虛擬機位長一致,更具體的實現(xiàn)可以閱讀 《java對象頭的HotSpot實現(xiàn)分析》
2、_metadata是一個聯(lián)合體,其中wideKlassOop和narrowOop都是指向InstanceKlass對象的指針,wide版是普通指針,narrow版是壓縮類指針(compressed Class pointer)
instanceOopDesc對象的創(chuàng)建過程
instanceOopDesc對象通過instanceKlass::allocate_instance進行創(chuàng)建,實現(xiàn)過程如下:
1、has_finalizer判斷當(dāng)前類是否包含不為空的finalize方法;
2、size_helper確定創(chuàng)建當(dāng)前對象需要分配多大內(nèi)存;
3、CollectedHeap::obj_allocate從堆中申請指定大小的內(nèi)存,并創(chuàng)建instanceOopDesc對象,實現(xiàn)如下:
4、如果當(dāng)前類重寫了finalize方法,且非空,需要把生成的對象封裝成Finalizer對象并添加到 Finalizer鏈表中,對象被GC時,如果是Finalizer對象,會將對象賦值到pending對象。Reference Handler線程會將pending對象push到queue中,Finalizer線程poll到對象,先刪除掉Finalizer鏈表中對應(yīng)的對象,然后再執(zhí)行對象的finalize方法;
365天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回復(fù)【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的java中Mark接口_JVM源码分析之Java对象头实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机械革命极光 Pro 游戏本上架:12
- 下一篇: java13种技术_JavaEE的13种