對(duì)象
內(nèi)存分配的兩種方式
指針碰撞
適用場(chǎng)合:堆內(nèi)存規(guī)整(即沒(méi)有內(nèi)存碎片)的情況下。
原理:用過(guò)的內(nèi)存全部整合到一邊,沒(méi)有用過(guò)的內(nèi)存放在另一邊,中間有一個(gè)分界指針,只需要向著沒(méi)用過(guò)的內(nèi)存方向?qū)⒃撝羔樢苿?dòng)對(duì)象內(nèi)存大小位置即可。
使用該分配方式的GC收集器:Serial, ParNew
空閑列表
適用場(chǎng)合:堆內(nèi)存不規(guī)整的情況下。
原理:虛擬機(jī)會(huì)維護(hù)一個(gè)列表,列表中會(huì)記錄哪些內(nèi)存塊是可用的,在分配的時(shí)候,找一塊兒足夠大的內(nèi)存塊兒來(lái)劃分給對(duì)象實(shí)例,最后更新列表記錄。
使用該分配方式的GC收集器:CMS
如何選擇
選擇以上兩種方式中的哪一種,取決于 Java 堆內(nèi)存是否規(guī)整。
而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標(biāo)記-清除",還是"標(biāo)記-整理"(也稱(chēng)作"標(biāo)記-壓縮"),值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的。
對(duì)象的創(chuàng)建過(guò)程
Java 對(duì)象的創(chuàng)建過(guò)程我建議最好是能默寫(xiě)出來(lái),并且要掌握每一步在做什么。
Step1:類(lèi)加載檢查
虛擬機(jī)遇到一條 new 指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類(lèi)的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類(lèi)是否已被加載過(guò)、解析和初始化過(guò)。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類(lèi)加載過(guò)程。
Step2:分配內(nèi)存
在類(lèi)加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類(lèi)加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來(lái)。
Step3:初始化零值
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類(lèi)型所對(duì)應(yīng)的零值。
Step4:設(shè)置對(duì)象頭
初始化零值完成之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例、如何才能找到類(lèi)的元數(shù)據(jù)信息、對(duì)象的哈希碼、對(duì)象的 GC 分代年齡等信息。 這些信息存放在對(duì)象頭中。 另外,根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同,如是否啟用偏向鎖等,對(duì)象頭會(huì)有不同的設(shè)置方式。
Step5:執(zhí)行 init 方法初始化
在上面工作都完成之后,從虛擬機(jī)的視角來(lái)看,一個(gè)新的對(duì)象已經(jīng)產(chǎn)生了,但從 Java 程序的視角來(lái)看,對(duì)象創(chuàng)建才剛開(kāi)始,<init>
方法還沒(méi)有執(zhí)行,所有的字段都還為零。所以一般來(lái)說(shuō),執(zhí)行 new 指令之后會(huì)接著執(zhí)行 <init>
方法,把對(duì)象按照程序員的意愿進(jìn)行初始化,這樣一個(gè)真正可用的對(duì)象才算完全產(chǎn)生出來(lái)
對(duì)象的內(nèi)存布局
可以劃分為三個(gè)部分:對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充(8bit倍數(shù))
-
虛擬機(jī)的對(duì)象頭:包括兩部分信息:
-
第一部分用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)(哈希碼、GC 分代年齡、鎖狀態(tài)標(biāo)志等等)
-
另一部分是類(lèi)型指針,即對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例。
-
-
實(shí)例數(shù)據(jù):是對(duì)象真正存儲(chǔ)的有效信息,也是在程序中所定義的各種類(lèi)型的字段內(nèi)容。
-
對(duì)齊填充:不是必然存在的,也沒(méi)有什么特別的含義,僅僅起占位作用。 因?yàn)?Hotspot 虛擬機(jī)的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是 8 字節(jié)的整數(shù)倍,換句話說(shuō)就是對(duì)象的大小必須是 8 字節(jié)的整數(shù)倍。而對(duì)象頭部分正好是 8 字節(jié)的倍數(shù)(1 倍或 2 倍),因此,當(dāng)對(duì)象實(shí)例數(shù)據(jù)部分沒(méi)有對(duì)齊時(shí),就需要通過(guò)對(duì)齊填充來(lái)補(bǔ)全。
對(duì)象的訪問(wèn)定位
對(duì)象的訪問(wèn)方式由虛擬機(jī)實(shí)現(xiàn)而定,目前主流的訪問(wèn)方式有:使用句柄、直接指針。
建立對(duì)象就是為了使用對(duì)象,我們的 Java 程序通過(guò)棧上的 reference引用 數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。
句柄的方式
如果使用句柄的話,那么 Java 堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference (引用)中存儲(chǔ)的就是對(duì)象的句柄地址。
流程就是: 引用 找到--》 句柄地址 找到--》 實(shí)際數(shù)據(jù)
句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與對(duì)象類(lèi)型數(shù)據(jù)各自的具體地址信息。
優(yōu)點(diǎn)
這種方式的好處是可以使對(duì)象的布局更加靈活,因?yàn)閷?duì)象數(shù)據(jù)可以在堆內(nèi)存中移動(dòng)而不影響句柄的引用。
缺點(diǎn)
訪問(wèn)對(duì)象需要兩次內(nèi)存訪問(wèn):首先是根據(jù)句柄找到對(duì)象的引用,然后再根據(jù)引用找到對(duì)象的實(shí)際數(shù)據(jù)。這會(huì)導(dǎo)致一些額外的性能開(kāi)銷(xiāo)。
直接指針
如果使用直接指針訪問(wèn),reference引用 中存儲(chǔ)的直接就是對(duì)象的地址。
流程就是: 引用 找到--》 實(shí)際數(shù)據(jù)
指針訪問(wèn)方式最大的好處:就是速度快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-718172.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-718172.html
到了這里,關(guān)于JVM面試題-JVM對(duì)象的創(chuàng)建過(guò)程、內(nèi)存分配、內(nèi)存布局、訪問(wèn)定位等問(wèn)題詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!