JVM運行時數據區--縱向補充--對象的實例化內存布局與訪問定位


對象的實例化

創建對象的方式

1.new:最常見的方式(本質是構造器)

     變形1 : Xxx的靜態方法

     變形2 : XxBuilder/XxoxFactory的靜態方法

2.Class的newInstance():反射的方式,只能調用空參的構造器,權限必須是public

3.Constructor的newInstance(Xxx):反射的方式,位於java.lang.reflect.Constructor<T> 可以調用空參、帶參的構造器,權限沒有要求

4.使用clone() :不調用任何構造器,當前類需要實現Cloneable接口,實現clone()

5.使用反序列化:從文件中、從網絡中獲取一個對象的二進制流

6.第三方庫Objenesis

創建對象的步驟

1.判斷對象對應的類是否加載、鏈接、初始化

虛擬機遇到一條new指令,首先去檢查這個指令的參數能否在Metaspace的常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被加載、解析和初始化。( 即判斷類元信息是否存在)。

如果沒有,那么在雙親委派模式下,使用當前類加載器以ClassLoader+包名+類名為Key進行查找對應的.class文件。如果沒有找到文件,則拋出ClassNotFoundException異常,如果找到,則進行類加載,並生成對應的Class類對象

2.為對象分配內存

首先計算對象占用空間大小,接着在堆中划分一塊內存給新對象。 如果實例成員變量是引用變量,僅分配引用變量空間即可,即4個字節大小。

  • 如果內存規整,使用指針碰撞
    如果內存是規整的,那么虛擬機將采用的是指針碰撞法(BumpThePointer)來為對象分配內存。意思是所有用過的內存在一邊,空閑的內存在另外一邊,中間放着一個指針作為分界點的指示器,分配內存就僅僅是把指針向空閑那邊挪動一段與對象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基於壓縮算法的,虛擬機采用這種分配方式。所以一般使用帶有compact (整理)過程的收集器時,使用指針碰撞。
  • 如果內存不規整,虛擬機需要維護一個列表,使用空閑列表分配
    如果內存不是規整的,已使用的內存和未使用的內存相互交錯,那么虛擬機將采用的是空閑列表法來為對象分配內存。意思是虛擬機維護了一個列表,記錄上哪些內存塊是可用的,再分配的時候從列表中找到一塊足夠大的空間划分給對象實例,並更新列表上的內容。這種分配方式成為“空閑列表(Free List) ”。

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

3.處理並發安全問題

在分配內存空間時,另外一個問題是及時保證new對象時候的線程安全性:創建對象是非常頻繁的操作,虛擬機需要解決並發問題。虛擬機采用 了兩種方式解決並發問題:

  • CAS ( Compare And Swap )失敗重試、區域加鎖:保證指針更新操作的原子性;
  • TLAB把內存分配的動作按照線程划分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩沖區,(TLAB ,Thread Local Allocation Buffer) 虛擬機是否使用TLAB,可以通過一XX:+/一UseTLAB參數來 設定。

4.初始化分配到的空間

Java給對象的屬性賦值的操作有如下四種:
① 屬性的默認初始化 
② 顯式初始化 
③ 代碼塊中初始化 
④ 構造器中初始化

這里提到的是第①種:初始化分配到的空間指的是①:所有屬性設置默認值,保證對象實例字段在不賦值時可以直接使用。

內存分配結束,虛擬機將分配到的內存空間都初始化為零值(不包括對象頭)。這一步保證了對象的實例字段在Java代碼中可以不用賦初始值就可以直接使用,程序能訪問到這些字段的數據類型所對應的零值。

5.設置對象的對象頭

將對象的所屬類(即類的元數據信息)、對象的HashCode和對象的GC信息、鎖信息等數據存儲在對象的對象頭中。這個過程的具體設置方式取決於JVM實現。

6.執行init方法進行初始化

在Java程序的視角看來,初始化才正式開始。初始化成員變量,執行實例化代碼塊,調用類的構造方法,並把堆內對象的首地址賦值給引用變量。 因此一般來說(由字節碼中是否跟隨有invokespecial指令所決定),new指令之 后會接着就是執行方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象才算完全創建出來。

 

對象的內存布局

 

對象頭(Header)

包含兩部分

  • 運行時元數據
    • 哈希值( HashCode )
    • GC分代年齡
    • 鎖狀態標志
    • 線程持有的鎖
    • 偏向線程ID
    • 偏向時間戳
  • 類型指針:指向類元數據的InstanceKlass,確定該對象所屬的類型

說明:如果是數組,還需記錄數組的長度

實例數據(Instance Data)

說明:它是對象真正存儲的有效信息,包括程序代碼中定義的各種類型的字段(包括從父類繼承下來的和本身擁有的字段) 規則:

  • 相同寬度的字段總被分配在一起
  • 父類中定義的變量會出現在子類之前
  • 如果CompactFields參數為true(默認為true),子類的窄變量可能插入到父類變量的空隙

對齊填充(Padding)

不是必須的,也沒特別含義,僅僅起到占位符作用

圖示小結

示例代碼:

public class CustomerTest {
    public static void main(String[] args) {
        Customer cust = new Customer();
    }
}

 

 

對象的訪問定位

 

訪問對象實例的主要方式有兩種:句柄訪問、直接指針(HotSpot采用)

句柄訪問方式

句柄定位方式優缺點:

缺點:占用空間、間接指向對象實例。

優點:定位穩定:當對象實例發生移動(垃圾回收算法,內存整理算法),則不需要修改reference到句柄的定位地址,僅需要在句柄內修改間接地址,重新定位到對象實例即可。

直接指針方式

 

 hotspot虛擬機采用的是直接指針方式。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM