1- 對(duì)象的創(chuàng)建
一個(gè)new的奇妙旅途(普通Java對(duì)象,不包括數(shù)組和Class對(duì)象)
A a = new B();
可以拆分成兩句
A a; a = new B();
第一句聲明的是A類型的一個(gè)引用贡珊,初始值為null最爬,第二句是創(chuàng)建了一個(gè)對(duì)象。
下面就說(shuō)明new B()
的到底干了什么门岔?
注意:上述為單線程模式下的對(duì)象創(chuàng)建爱致;如果為多線程模式下,創(chuàng)建的是主內(nèi)存共享的對(duì)象寒随,并發(fā)訪問可能發(fā)生糠悯,讀取到的是沒有進(jìn)行自定義初始化的不完整的對(duì)象(只完成了默認(rèn)初始化),因?yàn)?a = new B();
語(yǔ)句編譯成字節(jié)碼指令一般(取決于在構(gòu)造函數(shù)或者域中有沒有指定值)是兩條指令:new妻往,invokespecial互艾,其中new用來(lái)分配對(duì)象內(nèi)存空間并初始化默認(rèn)值,返回堆對(duì)象的引用讯泣,而invokespecial指令用來(lái)調(diào)用對(duì)象自定義初始化方法<init>纫普,而且編譯器允許這兩個(gè)指令之間的重排序。
上圖最后兩步對(duì)應(yīng)的是invokespecial指令好渠,前面對(duì)應(yīng)的是new指令昨稼。
對(duì)于單線程來(lái)說(shuō)JMM保證了單線程串行執(zhí)行結(jié)果的可見性順序,所以即使發(fā)生重排序同一線程的后續(xù)語(yǔ)句也能獲得完整初始化的對(duì)象拳锚。
1.1- 分配多少內(nèi)存假栓?
對(duì)象分配的內(nèi)存空間大小在類加載時(shí)便可以完全確定,詳情參見對(duì)象的內(nèi)存布局
指針碰撞霍掺、空閑列表
堆內(nèi)存的分配方式匾荆,堆內(nèi)存的分配方式和垃圾回收策略決定
1.1.1- 指針碰撞
- 當(dāng)使用Serial、ParNew等帶compact壓縮垃圾回收器進(jìn)行垃圾回收時(shí)(內(nèi)存區(qū)域是絕對(duì)規(guī)整的)
- 用過的內(nèi)存和沒有使用過的內(nèi)存分別放在兩邊抗楔,中間用一個(gè)指針(地址)進(jìn)行進(jìn)行標(biāo)示棋凳,分配內(nèi)存是拦坠,就是將這個(gè)指針向未使用的空間移動(dòng)對(duì)象大小的位置连躏,然后將對(duì)象分配在這個(gè)空出來(lái)的空間上。
1.1.2- 空閑列表
- 使用CMS等基于Mark-Sweep算法的垃圾回收器時(shí)
- 堆內(nèi)存空間不是規(guī)整的贞滨,用空閑列表來(lái)記錄對(duì)中哪些是可用的入热,分配內(nèi)存時(shí)就是將空閑列表中找到足夠大小的內(nèi)存空間來(lái)分配對(duì)象拍棕。
2- 對(duì)象的內(nèi)存布局
對(duì)象創(chuàng)建在內(nèi)存空間的布局。對(duì)象的內(nèi)存區(qū)域可以分為3塊:對(duì)象頭勺良、實(shí)例數(shù)據(jù)绰播、對(duì)齊填充。
2.1- 對(duì)象頭
分為兩個(gè)部分:存儲(chǔ)對(duì)象運(yùn)行時(shí)數(shù)據(jù)尚困、對(duì)象的類型指針蠢箩。
運(yùn)行時(shí)數(shù)據(jù):HashCode、GC分帶年齡事甜、鎖的信息谬泌。
類型指針:指向類的元數(shù)據(jù)(在方法區(qū)中)的指針,虛擬機(jī)通過這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例逻谦,但是不一定會(huì)存儲(chǔ)直接指針掌实,還會(huì)通過存儲(chǔ)句柄接持有類型指針;如果是數(shù)組對(duì)象邦马,對(duì)象頭還會(huì)有記錄數(shù)組長(zhǎng)度的數(shù)據(jù)贱鼻,因?yàn)樘摂M機(jī)從數(shù)組的元數(shù)據(jù)是無(wú)法確定數(shù)組的大小的。
補(bǔ)充:對(duì)象頭的數(shù)據(jù)大小為32位(32位虛擬機(jī))或64位(64位虛擬機(jī)未開啟壓縮)滋将,這個(gè)部分的數(shù)據(jù)的內(nèi)部結(jié)構(gòu)不是固定的邻悬,可以根據(jù)對(duì)象的狀態(tài)進(jìn)行復(fù)用。
類型字節(jié): byte 1字節(jié)耕渴、short 2字節(jié)拘悦、int 4字節(jié) 、 long 8字節(jié)橱脸、char 2字節(jié)(unicode編碼础米,可以存儲(chǔ)一個(gè)漢字)、float 4字節(jié) 添诉、double 8字節(jié)屁桑、boolean 1字節(jié)
2.2- 實(shí)例數(shù)據(jù)
- 這部分是對(duì)象中,實(shí)例域(而不是方法中局部變量)的數(shù)據(jù)內(nèi)容(其實(shí)也是對(duì)象的引用或基本變量類型的值)栏赴。
- 不僅子類中的數(shù)據(jù)蘑斧,而且父類的數(shù)據(jù)都要存儲(chǔ)起來(lái)。
- 存儲(chǔ)順序按照须眷,繼承關(guān)系竖瘾,和聲明順序依次存儲(chǔ);相同寬度的數(shù)據(jù)被分配在一起花颗。
- 但是為了節(jié)省空間捕传,會(huì)把子類較窄的數(shù)據(jù)插入到父類的空隙中。
2.3- 對(duì)齊填充數(shù)據(jù)
HotSpot 要求每個(gè)對(duì)象的起始地址必須是8字節(jié)的整數(shù)倍扩劝,換句話說(shuō)每個(gè)對(duì)象的大小必須是8字節(jié)的整數(shù)倍庸论,而對(duì)象頭正好是8字節(jié)的整數(shù)倍职辅,而對(duì)象的實(shí)例數(shù)據(jù)可能會(huì)有些寬度不同的數(shù)據(jù)沒有對(duì)齊,這是需要對(duì)齊數(shù)據(jù)進(jìn)行填充聂示。
3- 對(duì)象的訪問定位
關(guān)注的主要是對(duì)象空間中的類型指針是如何定位到類的元數(shù)據(jù)的問題域携。主要有兩種:句柄池、直接指針訪問鱼喉。
3.1- 句柄
句柄池的位置:java堆中一塊單獨(dú)的內(nèi)存空間秀鞭,里面放了很多句柄。
一個(gè)句柄包含兩個(gè)部分:到對(duì)象實(shí)例數(shù)據(jù)(堆上)的指針扛禽、到對(duì)象類型數(shù)據(jù)的指針(方法區(qū)上)气筋。
-
如何通過java棧幀中的變量表reference找到其對(duì)應(yīng)堆上對(duì)象數(shù)據(jù)的?
棧幀變量表reference中會(huì)保存對(duì)象的句柄地址旋圆,通過句柄地址就可以找到對(duì)象和類型的數(shù)據(jù)信息宠默。
優(yōu)點(diǎn)是:當(dāng)對(duì)象被移動(dòng)時(shí)(發(fā)生在垃圾回收時(shí)),不用修改reference的地址灵巧,只需修改句柄地址的對(duì)象實(shí)例的指針搀矫;缺點(diǎn):額外占用一部分內(nèi)存空間,而且需要進(jìn)行兩次指針訪問刻肄,而且這種操作很頻繁瓤球。
3.2- 直接指針訪問
- reference保存的是對(duì)象在java堆上的直接地址,而對(duì)象上會(huì)保存一個(gè)對(duì)其類型信息(方法區(qū))的指針
- 優(yōu)點(diǎn):速度更快敏弃,HotSpot就是使用的這種方式進(jìn)行定位的