HotSpot虛擬機對象探秘
對象的創(chuàng)建
在類加載檢查通過后,接下來虛擬機將為新生對象分配內(nèi)存渊胸。對象所需內(nèi)存的大小在類加載完成后便可以完全確定。
假設(shè)當Java堆中內(nèi)存時絕對規(guī)則的,即所有用過的內(nèi)存放在一邊踊东,空閑的在另一邊贰健,中間放著一個指針作為分界點的指示器胞四。那么分配內(nèi)存就是把指針向空閑區(qū)域挪動一段與對象大小相等的距離,這種分配方式成為“指針碰撞”伶椿。
當Java堆中的內(nèi)存并不是規(guī)則的辜伟,已使用的內(nèi)存和空閑的內(nèi)存相互交錯,虛擬機就必須維護一個列表脊另,記錄上那些內(nèi)存塊是可用的导狡。在分配時,從列表中找到一塊足夠大的空間劃分給對象實例尝蠕,并更新表上的記錄烘豌,這種分配方式稱為“空閑列表”。
選擇哪種分配方式由Java堆是否規(guī)整決定看彼,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定廊佩。因此,在使用Serial靖榕、ParNew等帶Compact過程的收集器時标锄,系統(tǒng)采用的分配算法是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時茁计,通常采用空閑列表料皇。
虛擬機如何解決在并發(fā)情況下頻繁創(chuàng)建對象帶來的線程不安全問題谓松?
解決這個問題有兩種方案,一種是分配內(nèi)存空間的動作進行同步處理——實際上虛擬機采用CAS(注)配上失敗重試的方式保證更新操作的原子性践剂;另一種是把內(nèi)存分配的動作按照線程劃分在不同的空間中進行鬼譬,即每個線程在Java堆中預先分配一小塊內(nèi)存,稱為本地線程緩沖(TLAB)逊脯。哪個線程要分配內(nèi)存优质,就在哪個線程的TLAB上分配,只有TLAB用完并分配新的TLAB時军洼,才需要同步鎖定巩螃。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數(shù)來設(shè)定匕争。
注:CAS避乏,Compare and Swap即比較并替換,設(shè)計并發(fā)算法時常用到的一種技術(shù)甘桑。CAS有三個操作數(shù):內(nèi)存值V拍皮、舊的預期值A(chǔ)、要修改的值B跑杭,當且僅當預期值A(chǔ)和內(nèi)存值V相同時春缕,將內(nèi)存值修改為B并返回true,否則什么都不做并返回false艘蹋。
CAS的內(nèi)容和底層原理推薦博文:http://blog.csdn.net/hsuxu/article/details/9467651
和 http://www.reibang.com/p/fb6e91b013cc
內(nèi)存分配完畢后,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)票灰,如果使用TLAB女阀,這一工作過程也可以提前至TLAB分配時進行。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用屑迂,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值浸策。
接下來,虛擬機要對對象進行必要的設(shè)置惹盼,例如這個對象是哪個類的實例庸汗、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼手报、對象的GC分代年齡等信息蚯舱。這些信息存放在對象的對象頭(Object Header)之中。根據(jù)虛擬機當前的運行狀態(tài)的不同掩蛤,如是否啟用偏向鎖等枉昏,對象頭會有不同的設(shè)置方式。關(guān)于對象頭的具體內(nèi)容揍鸟,稍后再做詳細介紹兄裂。
在上面工作都完成之后,從虛擬機的視角來看,一個新的對象已經(jīng)產(chǎn)生了晰奖,但從Java程序的視角來看谈撒,對象創(chuàng)建才剛剛開始——<init>方法還沒有執(zhí)行,所有的字段都還為零匾南。所以啃匿,一般來說(由字節(jié)碼中是否跟隨invokespecial指令所決定),執(zhí)行new指令之后會接著執(zhí)行<init>方法午衰,把對象按照程序員的意愿進行初始化立宜,這樣一個真正可用的對象才算完全產(chǎn)生出來。