JVM的內存分配原理

分配前的加載
當使用new關鍵字創建一個JAVA對象時,JVM首先會檢查這個New指令的參數是否在常量池中定位到一個類的符號引用,然後檢查與這個符號引用相對應的類是否已經成功經歷過加載、解析和初始化等步驟。當類完成裝載步驟之後,就已經完全可以確定出創建對象實例時所需要的內存空間大小,接下來JVM就會對其進行內存分配,以存儲所生成的對象實例。
分配內存
基於分代的概念,Java的堆區還可以劃分爲新生代(YoungGen)和老年代(OldGen),其中新生代又可以劃分爲Eden空間、From Survivor空間和To Survivor空間。在JVM的運行時,堆區和方法區是線程共享區域,因此在併發環境下從堆區中劃分內存空間是非線程安全的,所以要保證數據操作的原子性。基於線程安全的考慮,如果一個類在分配內存之前已經成功完成類裝載步驟之後,JVM就會優先選擇在TLAB(ThreadLocal Allocation 本地線程分配緩衝區)中爲對象實例分配內存空間,TLAB在Java堆區中是一塊線程私有區域,它包含在Eden空間內。一旦對象在TLAB中空間分配內存失敗,JVM就會嘗試通過使用加鎖機制來確保數據操作的原子性,從而直接在Eden空間中分配內存,如果當在Eden空間中也無法分配內存時,JVM就會執行Minor GC,直到最終可以在Eden空間中分配內存爲止。
初始化對象實例
當爲對象成功分配好所需的內存空間後,JVM所做的就是初始化對象實例。JVM首先會對分配後的內存空間進行零值初始化,這一步操作確保了對象的實例字段在Java代碼中不用賦初始值就能夠直接使用,程序能夠訪問到這些字段的數據類型所對應的零值。零值初始化之後,JVM就會初始化對象頭和實例數據(HotSpot中,內存空間所存儲的對象信息主要包含着兩個部分),最後將對象引用入棧,再更新PC寄存器 中的字節碼指令地址。
經過上述一系列的操作之後,一個Java對象實例纔算是真正的創建成功。

對象內存佈局與OOP—Klass模型
當成功進行零值初始化後,JVM接下來就會對對象進行實例化,而實例化也就是初始化對象頭和實例數據。

  1. 對象頭:對象頭中主要用於存儲Mark Word和元數據指針等數據,其中Mark Word主要用於存儲對象運行時的數據信息,比如HashCode、GC分代的年齡、線程持有的鎖、鎖狀態標誌、偏向線程Id等。元數據指針則是用於指向方法區中目標類的類型信息,也就是說通過元數據指針可以準確定位到當前對象的具體目標類型。
  2. 實例數據:實例數據主要用於存儲定義在當前對象中的各種類型的字段信息(包括派生於超類的字段信息),存儲在實例對象中的字段順序除了會與字段在Java類中定義的順序有關之外,還會受到JVM分配策略參數的影響。默認情況下,HotSpot會按照long/double,ints,shorts/chars、bytes/boolean,oops的分配順序進行分配。二進制位數相同的字段總是會被分配在一起。大家要注意,在超類中定義的變量很有可能會出現在派生類之前。

java中的類以及對象實例
OOP-Klass模型就是用於表示Java類以及對象實例的一種數據結構,其中OOP(Ordinary Object Pointer 普通對象指針)用於描述對象的實例信息,而Klass則用於描述對象實例的目標類型。在JVM中對象頭就是由對象instanceOopDesc來表示的(數組則是arrayOopDesc),而對象頭中的元數據指針所指向的當前對象的目標類則是由Klass中的instanceKlass對象來表示。
JVM如何通過棧幀中的對象引用訪問到其內部的對象實例呢?
JVM可以通過對象的引用準確定位到Java堆區的instanceOopDesc對象,這樣就可以成功訪問到對象的實例信息。當需要訪問目標對象的具體類型時,JVM則會通過存儲在instanceOopDesc中的元數據指針定位到存儲在方法區中的instanceKlass對象。

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