Hotspot虚拟机的对象
?
創(chuàng)建
Step1:類加載檢查
虛擬機(jī)遇到一條 new 指令時(shí),首先將去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到這個(gè)類的符號(hào)引用,并且檢查這個(gè)符號(hào)引用代表的類是否已被加載過(guò)、解析和初始化過(guò)。如果沒(méi)有,那必須先執(zhí)行相應(yīng)的類加載過(guò)程。
Step2:分配內(nèi)存
在類加載檢查通過(guò)后,接下來(lái)虛擬機(jī)將為新生對(duì)象分配內(nèi)存。對(duì)象所需的內(nèi)存大小在類加載完成后便可確定,為對(duì)象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從 Java 堆中劃分出來(lái)。分配方式有 “指針碰撞” 和 “空閑列表” 兩種,選擇那種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
內(nèi)存分配的兩種方式:
選擇以上兩種方式中的哪一種,取決于 Java 堆內(nèi)存是否規(guī)整。而 Java 堆內(nèi)存是否規(guī)整,取決于 GC 收集器的算法是"標(biāo)記-清除",還是"標(biāo)記-整理"(也稱作"標(biāo)記-壓縮"),值得注意的是,復(fù)制算法內(nèi)存也是規(guī)整的
在創(chuàng)建對(duì)象的時(shí)候有一個(gè)很重要的問(wèn)題,就是線程安全,因?yàn)樵趯?shí)際開(kāi)發(fā)過(guò)程中,創(chuàng)建對(duì)象是很頻繁的事情,作為虛擬機(jī)來(lái)說(shuō),必須要保證線程是安全的,通常來(lái)講,虛擬機(jī)采用兩種方式來(lái)保證線程安全:
CAS+失敗重試: CAS 是樂(lè)觀鎖的一種實(shí)現(xiàn)方式。所謂樂(lè)觀鎖就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性。
TLAB: 為每一個(gè)線程預(yù)先在 Eden 區(qū)分配一塊兒內(nèi)存,JVM 在給線程中的對(duì)象分配內(nèi)存時(shí),首先在 TLAB 分配,當(dāng)對(duì)象大于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已用盡時(shí),再采用上述的 CAS 進(jìn)行內(nèi)存分配
Step3:初始化零值
內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值(不包括對(duì)象頭),這一步操作保證了對(duì)象的實(shí)例字段在 Java 代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類型所對(duì)應(yīng)的零值。
Step4:設(shè)置對(duì)象頭
初始化零值完成之后,虛擬機(jī)要對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如這個(gè)對(duì)象是那個(gè)類的實(shí)例、如何才能找到類的元數(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)。
?
內(nèi)存布局
在 Hotspot 虛擬機(jī)中,對(duì)象在內(nèi)存中的布局可以分為 3 塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。
Hotspot 虛擬機(jī)的對(duì)象頭包括兩部分信息,第一部分用于存儲(chǔ)對(duì)象自身的自身運(yùn)行時(shí)數(shù)據(jù)(哈希碼、GC 分代年齡、鎖狀態(tài)標(biāo)志等等),另一部分是類型指針,即對(duì)象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是那個(gè)類的實(shí)例。
實(shí)例數(shù)據(jù)部分是對(duì)象真正存儲(chǔ)的有效信息,也是在程序中所定義的各種類型的字段內(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ǔ)全。
?
訪問(wèn)定位
建立對(duì)象就是為了使用對(duì)象,我們的 Java 程序通過(guò)棧上的 reference 數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。對(duì)象的訪問(wèn)方式由虛擬機(jī)實(shí)現(xiàn)而定,目前主流的訪問(wèn)方式有①使用句柄和②直接指針兩種:
句柄:?如果使用句柄的話,那么 Java 堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference 中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息;
直接指針:?如果使用直接指針訪問(wèn),那么 Java 堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息,而 reference 中存儲(chǔ)的直接就是對(duì)象的地址。
這兩種對(duì)象訪問(wèn)方式各有優(yōu)勢(shì)。使用句柄來(lái)訪問(wèn)的最大好處是 reference 中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而 reference 本身不需要修改。使用直接指針訪問(wèn)方式最大的好處就是速度快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷。
?
總結(jié)
以上是生活随笔為你收集整理的Hotspot虚拟机的对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: leetcode198 打家劫舍
- 下一篇: 贪吃蛇源代码111