詳述Java對象創建

  Java是一門面向對象的語言,Java程序運行過程中無時無刻都有對象被創建出來。在語言層面上,創建對象(克隆、反序列化)就是一個new關鍵字而已,但是虛擬機層面上卻不是如此。我們看一下在虛擬機層面上創建對象的步驟:
  (1)虛擬機遇到一條new指令,首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被加載、解析和初始化。如果沒有,那麼必須先執行類的初始化過程。

  (2)類加載檢查通過後,虛擬機爲新生對象分配內存。對象所需內存大小在類加載完成後便可以完全確定,爲對象分配空間無非就是從Java堆中劃分出一塊確定大小的內存而已。這個地方會有兩個問題:
  ①如果內存是規整的,那麼虛擬機將採用的是指針碰撞法來爲對象分配內存。意思是所有用過的內存在一邊,空閒的內存在另外一邊,中間放着一個指針作爲分界點的指示器,分配內存就僅僅是把指針向空閒那邊挪動一段與對象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基於壓縮算法的,虛擬機採用這種分配方式。
  ②如果內存不是規整的,已使用的內存和未使用的內存相互交錯,那麼虛擬機將採用的是空閒列表法來爲對象分配內存。意思是虛擬機維護了一個列表,記錄上哪些內存塊是可用的,再分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的內容。如果垃圾收集器選擇的是CMS這種基於標記-清除算法的,虛擬機採用這種分配方式。
  另外一個問題及時保證new對象時候的線程安全性。因爲可能出現虛擬機正在給對象A分配內存,指針還沒有來得及修改,對象B又同時使用了原來的指針來分配內存的情況。虛擬機採用了CAS配上失敗重試的方式保證更新更新操作的原子性和TLAB兩種方式來解決這個問題。

照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。哪個線程需要分配內存,就在哪個線程的TLAB上分配。虛擬機是否使用TLAB,可以通過-XX:+/-UseTLAB參數來設定。這麼做的目的之一,也是爲了併發創建一個對象時,保證創建對象的線程安全性。TLAB比較小,直接在TLAB上分配內存的方式稱爲快速分配方式,而TLAB大小不夠,導致內存被分配在Eden區的內存分配方式稱爲慢速分配方式。

   (3)內存分配結束,虛擬機將分配到的內存空間都初始化爲零值(不包括對象頭)。這一步保證了對象的實例字段在Java代碼中可以不用賦初始值就可以直接使用,程序能訪問到這些字段的數據類型所對應的零值。
  (4)對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息,這些信息存放在對象的對象頭中。
  (5)執行<init>方法,把對象按照程序員的意願進行初始化。

  到此,一個真正可用的對象纔算完全產生出來。
  上述建立對象是爲了使用對象,Java程序需要通過棧上的reference(引用)數據來操作堆上的具體對象。比如我們寫了一句:

Object obj = new Object();

  在new Object()之後,其實有兩部分內容,一部分是類數據(比如代表類的Class對象),一部分是實例數據。
  由於reference在Java虛擬機規範中只是一個指向對象new Object()的引用obj,並沒有規定obj應該通過何種方式去定位,以及訪問堆中對象的具體位置,所以對象訪問方式也是取決於虛擬機而定的。主流方式有兩種:
  (1)句柄訪問。Java堆中劃分出一塊句柄池,obj指向的是對象的句柄地址,句柄中則包含了類數據的地址和實例數據的地址
  (2)指針訪問。對象中存儲所有的實例數據和類數據的地址,obj指向的是這個對象
  HotSpot虛擬機採用的是後者,不過前者的對象訪問方式也是十分常見的。  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章