深入理解Java虛擬機(jī) 2.3.1章節(jié)筆記
在語言層面突雪,對(duì)象的創(chuàng)建通常使用new關(guān)鍵字實(shí)現(xiàn)擦秽,這篇筆記主要記錄虛擬機(jī)中一個(gè)對(duì)象的創(chuàng)建過程腌紧。
流程圖如下:
1.當(dāng)虛擬機(jī)接收到new指令時(shí)期丰,首先會(huì)使用new指令的參數(shù)去常量池中定位類的符號(hào)引用欢瞪。
2.檢查這個(gè)類是否已經(jīng)被記載并且初始化。
3.如果沒有加載過這個(gè)類碉纺,需要執(zhí)行相應(yīng)的類加載的過程船万。
4.為新對(duì)象分配內(nèi)存空間
-
分配空間有兩種主要的方式- 指針碰撞(Bump the Pointer) 和 空閑列表(Free List)。
兩種方式的選擇取決于內(nèi)存空間是否完全規(guī)整,即堆內(nèi)存中不存在已分配空間和空閑空間交錯(cuò)的情況惜辑。
內(nèi)存空間是否完全規(guī)整,取決于虛擬機(jī)的垃圾回收(GC)是否帶有壓縮整理功能(Serial疫赎, ParNew帶有Compact盛撑, 基于Mark-Sweep的CMS則沒有)
指針碰撞:假設(shè)jvm堆內(nèi)存當(dāng)前是絕對(duì)規(guī)整的,所有已分配對(duì)象都在一邊捧搞,空閑空間在另一邊抵卫,存在一個(gè)指針作為分界點(diǎn)的指示器,那么每一次分配內(nèi)存都只需要將指針往空閑區(qū)域移動(dòng)要分配的對(duì)象大小的范圍即可胎撇。
空閑列表:由于堆內(nèi)存中已分配對(duì)象和空閑區(qū)域相互交錯(cuò)介粘,虛擬機(jī)需要維護(hù)一個(gè)列表來記錄哪些內(nèi)存塊是可用的,在分配對(duì)象時(shí)虛擬機(jī)會(huì)從列表中找出一塊可用空間晚树,并維護(hù)列表的記錄姻采。
-
對(duì)象分配過程中的線程安全問題
對(duì)象的創(chuàng)建是一個(gè)非常頻繁的行為,僅僅是修改一個(gè)指針指向的地址的操作在并發(fā)情況下也不是線程安全的行為爵憎,會(huì)出現(xiàn)正在給對(duì)象A分配內(nèi)存慨亲,指針還沒來得及修改,對(duì)象B又使用了原來的指針來分配內(nèi)存宝鼓。
-
兩種解決方案:
- 對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理刑棵,虛擬機(jī)上采用CAS配合失敗重試來保證操作的原子性
- TLAB(Thread Local Allocation Buffer)本地線程分配緩沖,每個(gè)線程在Java堆中預(yù)先分配一塊空間來進(jìn)行實(shí)際對(duì)象的內(nèi)存分配愚铡,當(dāng)TLAB用完需要新的TLAB時(shí)才進(jìn)行同步鎖定蛉签。
5.內(nèi)存分配完成后根據(jù)配置可能會(huì)需要將分配到的內(nèi)存空間初始化為0值。
- 如果使用TLAB,可以提前至在TLAB中執(zhí)行這一操作碍舍。
- 這一操作確保了對(duì)象的實(shí)例在java中可以不賦初始值而直接使用柠座。因此程序可以訪問到這些字段所對(duì)應(yīng)的默認(rèn)值
6.設(shè)置對(duì)象頭(哪個(gè)類的實(shí)例,hash code乒验,GC代年齡等)