天天看點

普通Java對象的建立過程詳解類加載過程配置設定記憶體初始化執行方法

普通Java對象的建立過程詳解

  • 類加載過程
  • 配置設定記憶體
      • 指針碰撞
      • 空閑清單
  • 初始化
  • 執行方法

Java是一門面向對象的程式設計語言,在Java程式運作過程中無時無刻都有對象被建立出來。在語言層面上,建立對象(例如克隆、反序列化)通常僅僅是一個new關鍵字而已,而在虛拟機中,普通的Java對象的建立又是怎樣的一個過程呢?接下來我們一層層揭開面紗,探索new對象建立過程背後的秘密。

類加載過程

虛拟機遇到一條new指令是,首先将去檢查這個指令的參數是否能在常量池中定位到一個類的符号引用,并且檢查這個符号引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。具體的類加載過程如下:

配置設定記憶體

在類加載檢查通過後,接下來虛拟機将為新生對象配置設定記憶體。對象所需記憶體的大小在類加載完成後便可完全确定(具體可看對象的記憶體布局)。主要有兩種配置設定記憶體的方式:指針碰撞和空閑清單

指針碰撞

假設Java堆中記憶體是絕對規整的,所有用過的記憶體都放在一邊,空閑的記憶體放在另一邊,中間放着一個指針作為分界點的訓示器,那所配置設定記憶體就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離即可。

空閑清單

如果Java堆中的記憶體并不是規整的,已使用的記憶體和空閑的記憶體互相交錯,那就沒辦法簡單地進行指針碰撞了,虛拟機就必須維護一個清單,記錄上哪些記憶體塊是可用的,在配置設定的時候從清單中找到一個足夠大的空間劃分給對象執行個體,并更新清單上的記錄。

選擇哪種配置設定方式由Java堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。

補充:如何在并發情況下解決對象建立的線程不安全問題?

答:對象建立在虛拟機中是非常頻繁的行為,即使是僅僅修改一個指針所指向的位置,在并發情況下也并不是線程安全的,可能出現正在給對象A配置設定記憶體,指針還沒來得及修改,對象B又同時使用了原來的指針來配置設定記憶體的情況。解決這個問題有兩種方案,

(1)一種是對配置設定記憶體空間的動作進行同步處理-------實際上虛拟機采用CAS配上失敗重試的方式保證更新操作的原子性;(2)另一種是把記憶體配置設定的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先配置設定一小塊記憶體,稱為本地線程配置設定緩沖(Thread Local Allocation Buffer,TLAB)。哪個線程要配置設定記憶體,就在哪個線程的TLAB上配置設定,隻有TLAB用完并配置設定新的TLAB時,才需要同步鎖定。

初始化

記憶體配置設定完成後,虛拟機需要将配置設定到的記憶體空間都初始化為零值(不包括對象頭),如果使用TLAB,這一工作過程也可以提前至TLAB配置設定時進行。接下來,虛拟機要對對象的對象頭進行必要的設定。

執行方法

在上述工作都完成之後,從虛拟機的視角來看,一個新的對象已經産生了,但從Java程式的視角來看,對象建立才剛剛開始-----方法還沒有執行,所有的字段都還為零。一般來說,執行new指令之後會接着執行方法,把對象按照程式員的意願進行初始化,這樣一個真正可用的對象才算完全産生出來。

參考:

《深入了解Java虛拟機》 周志明著