Java是一門面向對象的語言,創建對象通常僅僅是一個new關鍵字而已,而在虛擬機中,對象的創建又是怎樣的一個過程呢?
Java對象創建過程
虛擬機遇到一個new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有那麼必須先執行相應的類加載過程。
在類加載檢查通過後,接下來虛擬機將爲新生對象分配內存(所需內存大小在類加載完成後便可以完全確定)。爲對象分配空間的任務等同於把一塊確定大小的內存從Java堆中劃分出來。
除了如何劃分可用空間之外,還需要考慮到併發情況是否爲線程安全。
內存分配完成後,虛擬機需要將分配到的內存空間初始化爲零值(不包括對象頭),如果使用TLAB,這一過程也可以提前至TLAB(Thread Local Allocation Buffer)分配時進行。
虛擬機堆對象進行必要的設置,比如該對象是哪個類的實例、如果才能找到類的元數據信息,對象的GC分代年齡等信息。
上面的過程完成後:
- 從虛擬機的視角來看,一個新的對象已經產生
- 從Java程序視角來看,對象創建剛剛開始
JVM分配內存的過程
JVM分配內存的原則主要是Java堆中是否有絕對規整的內存,即所有用過的內存放一邊,空閒的內存放一邊。
指針碰撞(Bump the Pointer):在使用過的內存還空閒的內存中間存放一個指針作爲分界點的指示器,需要分配內存時,只需要將那個指針向空閒內存中間移動與對象大小相等的距離便可。
空閒列表(Free List):維護一張表,用於記錄那些內存塊是可用的,在分配的時候,從列表中找到一塊足夠大的空間劃分給實例,並更新列表上的記錄。
Java堆是否規整取決於垃圾收集器是否帶有壓縮整理功能。
因此在使用Serial, ParNew等帶有Compact過程的收集器時,系統採用的分配算法是指針碰撞;
而使用CMS這種基於Mark-Sweep算法的收集器時,通常採用空閒列表。
JVM對線程安全的處理
在併發情況下,如果創建的對象不是線程安全,JVM目前有兩種解決方案。
- 對分配空間的動作進行同步處理:即採用CAS配上失敗重試的方式保證更新操作的原子性;
- 把內存分配的動作按照線程劃分在不同的空間之中進行:即每個線程在Java堆中預先分配一小塊內存,稱爲TLAB,哪個線程要分配內存,就在哪個TLAB上分配,只有TLAB用完並分配新的TLAB時,才需要同步鎖定。
虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB
參數來設定。