天天看點

11. 對象執行個體化、記憶體布局與通路定位

文章目錄

    • 對象執行個體化、記憶體布局與通路定位
      • 1. 對象執行個體化
        • 1.1 建立對象的方式
        • 1.2 建立對象的步驟
            • 一、判斷對象對應的類是否加載、連結、初始化
            • 二、為對象配置設定記憶體
            • 三、處理并發問題
            • 四、初始化配置設定到的空間
            • 五、設定對象的對象頭
            • 六、執行init方法進行初始化
            • 小結
      • 2. 對象記憶體布局
        • 2.1 小結
      • 3. 對象的通路定位
        • 3.1 對象通路的兩種方式
            • 一、句柄通路
            • 二、直接指針(HotSpot采用)

對象執行個體化、記憶體布局與通路定位

  • 對象在JVM中是怎麼存儲的?
  • 對象頭資訊裡面有哪些東西?
  • Java對象頭裡有什麼?

1. 對象執行個體化

對象建立的方式

建立對象的步驟

開始說:
11. 對象執行個體化、記憶體布局與通路定位

1.1 建立對象的方式

  1. new:最常見的方式、

    單例類中調用getInstance的靜态類方法

    XXXFactory的靜态方法

  2. Class的newInstance方法

    :在JDK9裡面被标記為過時的方法,因為隻能調用空參構造器。
  3. Constructor的newInstance(XXX)

    :反射的方式,可以調用空參的,或者帶參的構造器。
  4. 使用clone():不調用任何的構造器,要求目前的類需要實作

    Cloneable接

    口并重寫其中的clone方法。
  5. 使用序列化:序列化一般用于Socket的網絡傳輸。
  6. 第三方庫 Objenesis

1.2 建立對象的步驟

一、判斷對象對應的類是否加載、連結、初始化

  1. 虛拟機遇到一條new指令,

    首先去檢查這個指令的參數能否在Metaspace的常量池中定位到一個類的符号引用,并且檢查這個符号引用代表的類是否已經被加載,解析和初始化。

    (即判斷類元資訊是否存在)。
  2. 如果沒有,

    那麼在雙親委派模式下,使用目前類加載器以ClassLoader + 包名 + 類名為key進行查找對應的 .class檔案,如果沒有找到檔案,則抛出ClassNotFoundException異常,如果找到,則進行類加載,并生成對應的Class對象。

二、為對象配置設定記憶體

首先計算對象占用空間的大小,接着在堆中劃分一塊記憶體給新對象。

如果執行個體成員變量是引用變量,僅配置設定引用變量空間即可,即4個位元組大小。

  1. 如果記憶體是規整的:
    • 虛拟機将采用的是

      指針碰撞法(Bump The Point)

      來為對象配置設定記憶體。
    • 意思是所有用過的記憶體在一邊,空閑的記憶體放另外一邊,中間放着一個指針作為分界點的訓示器,配置設定記憶體就僅僅是把指針指向空閑那邊挪動一段與對象大小相等的距離罷了。

      如果垃圾收集器選擇的是Serial ,ParNew這種基于壓縮算法的,虛拟機采用這種配置設定方式。一般使用帶Compact(整理)過程的收集器時,使用指針碰撞。

  2. 如果記憶體不規整,已使用的記憶體和未使用的記憶體互相交錯:
    • 虛拟機将采用的是空閑清單來為對象配置設定記憶體。
    • 空閑清單配置設定:
      • 意思是虛拟機維護了一個清單,記錄上那些記憶體塊是可用的,再配置設定的時候從清單中找到一塊足夠大的空間劃分給對象執行個體,并更新清單上的内容。

        這種配置設定方式成為了 “空閑清單(Free List)”

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

三、處理并發問題

  1. 采用CAS配上失敗重試保證更新的原子性
  2. 每個線程預先配置設定一塊TLAB

    • 通過設定 -XX:+UseTLAB參數來設定(區域加鎖機制)

    • 在Eden區給每個線程配置設定一塊區域

四、初始化配置設定到的空間

給對象屬性指派的操作
  1. 屬性的預設初始化
  2. 顯示初始化
  3. 代碼塊中的初始化
  4. 構造器初始化
  5. 所有屬性設定預設值,保證對象執行個體字段在不指派可以直接使用。

五、設定對象的對象頭

将對象的所屬類(即類的中繼資料資訊)、對象的HashCode和對象的GC資訊、鎖資訊等資料存儲在對象的對象頭中。

這個過程的具體設定方式取決于JVM實作。

六、執行init方法進行初始化

  1. 在Java程式的視角看來,初始化才正式開始。初始化成員變量,執行執行個體化代碼塊,調用類的構造方法,并把堆内對象的首位址指派給引用變量。
  2. 是以一般來說(由位元組碼中跟随

    invokespecial

    指令所決定),new指令之後會接着就是執行方法,把對象按照程式員的意願進行初始化,這樣一個真正可用的對象才算完成建立出來。

小結

  1. 加載類元資訊
  2. 為對象配置設定記憶體
  3. 處理并發問題
  4. 屬性的預設初始化(零值初始化)
  5. 設定對象頭資訊

  6. 屬性的顯示初始化、代碼塊中初始化、構造器中初始化

2. 對象記憶體布局

  1. 類型指針

    指向的其實是方法區中存放的類元資訊
11. 對象執行個體化、記憶體布局與通路定位

2.1 小結

11. 對象執行個體化、記憶體布局與通路定位

3. 對象的通路定位

JVM是如何通過棧幀中的對象引用通路到其内部的對象執行個體呢?
11. 對象執行個體化、記憶體布局與通路定位

3.1 對象通路的兩種方式

一、句柄通路

  1. 句柄通路就是說棧的局部變量表中,記錄的對象的引用,然後在堆空間中開辟了一塊空間,也就是句柄池。
  2. 優點:
    1. reference中存儲穩定句柄位址,對象被移動(垃圾收集時移動對象很普遍)時隻會改變句柄中執行個體資料指針即可,reference本身不需要被修改。

11. 對象執行個體化、記憶體布局與通路定位

二、直接指針(HotSpot采用)

直接指針是局部變量表中的引用,直接指向堆中的執行個體,在對象執行個體中有類型指針,指向的是方法區中的對象類型資料。
11. 對象執行個體化、記憶體布局與通路定位