1. 程式人生 > >深入理解Java虛擬機器----第2章 Java記憶體區域與記憶體溢位異常

深入理解Java虛擬機器----第2章 Java記憶體區域與記憶體溢位異常

第2章 Java記憶體區域與記憶體溢位異常

2.3 HotSpot 虛擬機器物件探祕
2.3.1 物件的建立
Java作為一門面向物件語言,在程式執行過程中會產生大量的物件,在語言層面上來看,建立一個物件僅僅需要一個new關鍵字即可,但是在虛擬機器中建立一個普通的Java物件(不包括Class物件和陣列)的過程你是否瞭解?
當虛擬機器遇到一條new指令的時候,會首先去檢查一下在常量池中能否定位到一個類的符號引用和指令引數相匹配,並檢查這個引用符號所代表的類是否已經執行過被載入、被解析、被初始化的過程,如果沒有就需要對這個類進行類載入。
檢查通過後,虛擬機器開始為這個新生的物件分配記憶體。這個物件所需要的記憶體大小在類載入完成之後即可確定下來,為物件分配記憶體實在堆上進行的,也可以理解為將堆中一塊記憶體從堆中劃分出來。而在堆中為物件分配記憶體有兩種方式,一種是“指標碰撞”,顧名思義當堆中的記憶體時規整的時候(即所有被使用的記憶體放在一邊,所有空閒的記憶體放在另一邊),只需要將作為空閒記憶體和使用記憶體分界線的指標向空閒記憶體那一邊移動一段與所需要分配的記憶體相同的大小即可。另外一種叫“空閒列表”,就是當堆的記憶體不是規整的時候,JVM會維護一個列表記錄哪塊記憶體是可以使用的,哪塊記憶體已經被佔用了,分配記憶體就是從列表上找一塊足夠大的空間分配給這個物件,這種方式有一個缺點,就是如果我們需要一個100K的空間但是這個時候堆上剩下的空間有101K,但是這101K分為了90K和11K兩塊,由於沒有連續的空間大於等於100K,所以這個時候明明空間夠用但是還是會報OutOfMemoryError的異常,而記憶體是否規整則取決於垃圾收集器所採用的收集演算法是標記-清除還是標記-整理又或者是暫停-複製。
除了分配空間問題,我們還要考慮一個問題,要知道物件的建立在虛擬機器中是一個非常頻繁的行為,僅僅修改一個指標的位置,在併發的情況下並不是執行緒安全的,很有可能會出現當A申請了記憶體之後指標還沒有來得及修改,這個時候B就使用了相同的記憶體地址進行了分配。而解決這個問題有兩個辦法,第一個是我們上一篇文章提到過得TLAB(本地執行緒分派緩衝),就是將堆的部分空閒分配為執行緒私有的,而物件建立的時候分配空間是在私有的緩衝區中進行的,這樣也就保證了執行緒安全,當然如果TLAB的空間用完了需要申請更多的空間,這個時候虛擬機器會進行同步鎖定,以保證執行緒安全,是否使用TLAB可以通過配置-XX:+/-UseTLAB的值來設定。第二個辦法是對記憶體分配的行為進行了同步處理,同時加入失敗重試的方式保證了記憶體更新操作的原子性。
在記憶體分配完成後,虛擬機器需要將分配的空間(不包括物件頭,這個後面會講)初始化為零值,當使用了TLAB的時候,這個步驟可以在TLAB分配的時候就進行。這一操作保證了我們物件的成員變數不賦初值就可以直接使用,這個時候他們的值就是資料型別所對應的零值。
初始化零值之後,虛擬機器會對這個物件進行必要的設定,如這個物件是來自哪個類的例項、類的元資料資訊、物件的hashcode、物件的GC年齡分代等,這些資訊都是放在物件的物件頭之中的。
在完成這一系列的工作之後在虛擬機器的視角一個物件已經建立成功了,但是從Java程式的視角來看,物件的建立還沒有結束,方法還沒有執行,這裡我的理解init方法似乎和構造方法的作用差不多,但是不是一回事(先執行init方法後執行構造方法),在init方法裡面會按照我們的意願對成員變數進行初始化,這樣一個物件就真正的產生了。