<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      深入理解Java對(duì)象:從創(chuàng)建到內(nèi)存訪問(wèn)的JVM底層機(jī)制

      先去看看這篇博客了解下運(yùn)行時(shí)JVM數(shù)據(jù)區(qū)域,然后再回來(lái)看下面內(nèi)容,??????記得先贊后看效果翻倍?????? ~

      引言

      在Java開(kāi)發(fā)中,new關(guān)鍵字是我們創(chuàng)建對(duì)象最常用的方式。然而,在這簡(jiǎn)單的操作背后,JVM進(jìn)行了一系列復(fù)雜而精妙的操作。許多開(kāi)發(fā)者雖然每天都在創(chuàng)建對(duì)象,但對(duì)于對(duì)象在JVM中是如何被創(chuàng)建、如何在內(nèi)存中布局以及如何被訪問(wèn)的底層細(xì)節(jié)知之甚少。理解這些底層機(jī)制不僅有助于我們編寫(xiě)更高效的代碼,還能在性能調(diào)優(yōu)和故障排查時(shí)提供關(guān)鍵洞察,幫助我們更好地理解Java程序的運(yùn)行原理。

      本文將深入JVM層面,全面解析Java對(duì)象的完整生命周期:從創(chuàng)建過(guò)程、內(nèi)存布局到訪問(wèn)定位方式。通過(guò)本文,您將獲得對(duì)Java對(duì)象在JVM中表現(xiàn)的全面認(rèn)識(shí)。

      一、對(duì)象的創(chuàng)建過(guò)程

      1.1 類(lèi)加載檢查

      當(dāng)JVM遇到一條new指令時(shí)(例如new MyClass()),它首先檢查這個(gè)指令的參數(shù)是否能在運(yùn)行時(shí)常量池中定位到一個(gè)類(lèi)的符號(hào)引用。

      • 檢查內(nèi)容:檢查這個(gè)符號(hào)引用代表的類(lèi)是否已被加載、解析和初始化過(guò)
      • 如果未加載:如果JVM發(fā)現(xiàn)這個(gè)類(lèi)還沒(méi)有被加載,它會(huì)立即先執(zhí)行類(lèi)的加載過(guò)程(Loading → Linking → Initialization)。這是一個(gè)相對(duì)耗時(shí)的操作,包括讀取類(lèi)文件、驗(yàn)證、準(zhǔn)備解析等步驟
      • 如果已加載:則直接進(jìn)入下一步,為新生對(duì)象分配內(nèi)存

      這一步確保了對(duì)象一定是基于一個(gè)已被JVM完全知曉和驗(yàn)證的類(lèi)創(chuàng)建的。

      1.2 內(nèi)存分配

      在類(lèi)加載檢查通過(guò)后,JVM將為新生對(duì)象分配內(nèi)存。所謂分配內(nèi)存,就是從Java堆中劃出一塊確定大小的內(nèi)存空間給這個(gè)新對(duì)象。分配方式取決于Java堆是否規(guī)整,而堆是否規(guī)整又由所采用的垃圾收集器是否帶有空間壓縮整理的能力決定。

      主要有兩種分配方式:

      a) 指針碰撞

      • 條件:假設(shè)Java堆的內(nèi)存是絕對(duì)規(guī)整的,所有用過(guò)的內(nèi)存放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器
      • 操作:分配內(nèi)存僅僅就是把那個(gè)指針向空閑空間方向挪動(dòng)一段與對(duì)象大小相等的距離
      • 收集器:Serial, ParNew等帶有壓縮整理功能的收集器

      b) 空閑列表

      • 條件:如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò)
      • 操作:JVM必須維護(hù)一個(gè)列表,記錄哪些內(nèi)存塊是可用的。在分配時(shí),從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄
      • 收集器:CMS這種基于標(biāo)記-清除算法的收集器

      內(nèi)存分配中的并發(fā)問(wèn)題

      對(duì)象創(chuàng)建在JVM中非常頻繁,即使在單線程環(huán)境下,僅僅修改一個(gè)指針?biāo)赶虻奈恢茫诓l(fā)情況下也并不是線程安全的。可能出現(xiàn)正在給對(duì)象A分配內(nèi)存,指針還沒(méi)來(lái)得及修改,對(duì)象B又同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存。

      JVM采用了兩種方案來(lái)解決這個(gè)問(wèn)題:

      • CAS + 失敗重試:對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理,采用比較并交換(Compare And Swap)算法保證原子性。這是現(xiàn)代虛擬機(jī)普遍采用的方式
      • TLAB本地線程分配緩沖。每個(gè)線程在Java堆中預(yù)先分配一小塊私有內(nèi)存。哪個(gè)線程要分配內(nèi)存,就在自己的TLAB上分配,只有TLAB用完并分配新的TLAB時(shí),才需要同步鎖定。可以通過(guò)-XX:+/-UseTLAB參數(shù)來(lái)設(shè)定是否啟用

      1.3 內(nèi)存空間初始化(零值初始化)

      內(nèi)存分配完成后,JVM會(huì)將分配到的內(nèi)存空間(不包括對(duì)象頭)都初始化為零值(0, null, false等)。

      • 目的:這步操作保證了對(duì)象的實(shí)例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問(wèn)到這些字段的數(shù)據(jù)類(lèi)型所對(duì)應(yīng)的零值
      • 例如:int會(huì)初始化為0,boolean初始化為false,所有引用類(lèi)型會(huì)初始化為null

      注意:此時(shí)對(duì)象還沒(méi)有開(kāi)始執(zhí)行Java代碼中定義的構(gòu)造方法

      1.4 設(shè)置對(duì)象頭

      Java對(duì)象在內(nèi)存中的存儲(chǔ)布局可以分為三部分:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充

      在零值初始化之后,JVM需要對(duì)這個(gè)新生對(duì)象的對(duì)象頭進(jìn)行設(shè)置。對(duì)象頭包含兩類(lèi)信息:

      • Mark Word:用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)

        • 哈希碼
        • GC分代年齡
        • 鎖狀態(tài)標(biāo)志
        • 線程持有的鎖
        • 偏向線程ID
        • 偏向時(shí)間戳
        • 等等
        • 這部分?jǐn)?shù)據(jù)在32位和64位的虛擬機(jī)中分別為32bit和64bit,它的結(jié)構(gòu)是非固定的,會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用自己的存儲(chǔ)空間,以節(jié)省空間
      • 類(lèi)型指針:即對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針。JVM通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例

        • 并不是所有的虛擬機(jī)實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類(lèi)型指針(即通過(guò)指針訪問(wèn)對(duì)象類(lèi)型),這取決于對(duì)象的訪問(wèn)定位方式
      • 數(shù)組長(zhǎng)度:如果對(duì)象是一個(gè)Java數(shù)組,那在對(duì)象頭中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)

      1.5 執(zhí)行構(gòu)造方法

      從JVM的視角看,執(zhí)行構(gòu)造方法<init>是對(duì)象創(chuàng)建的最后一步

      • <init>方法:Java編譯器會(huì)將實(shí)例變量初始化器(例如int a = 123;)和構(gòu)造方法塊{})的代碼,收集合并到一個(gè)名為<init>的特殊方法中
      • 執(zhí)行過(guò)程:JVM執(zhí)行<init>方法,按照開(kāi)發(fā)者的意圖對(duì)對(duì)象進(jìn)行初始化,也就是為對(duì)象的字段賦予程序員真正想要的初始值
      • <clinit>的區(qū)別<init>是實(shí)例構(gòu)造器,用于初始化對(duì)象。而<clinit>是類(lèi)構(gòu)造器,用于初始化類(lèi)變量(static變量)

      直到這一步,一個(gè)真正可用的、符合開(kāi)發(fā)者預(yù)期的對(duì)象才被完全構(gòu)造出來(lái)。

      flowchart TD A["Java代碼: new MyClass()"] --> B["JVM字節(jié)碼: new指令"] B --> C{類(lèi)加載檢查} C -- 已加載? --> E[內(nèi)存分配] C -- 未加載 --> D["執(zhí)行類(lèi)加載過(guò)程<br>Loading → Linking → Initialization"] --> E subgraph 內(nèi)存分配策略 direction LR E1[指針碰撞<br>(堆規(guī)整)] E2[空閑列表<br>(堆不規(guī)整)] end E --> E1 E --> E2 E1 --> F[內(nèi)存空間零值初始化] E2 --> F[內(nèi)存空間零值初始化] F --> G[設(shè)置對(duì)象頭<br>Mark Word 和 類(lèi)型指針] G --> H[執(zhí)行構(gòu)造方法<init>] H --> I[對(duì)象創(chuàng)建完成]

      二、對(duì)象的內(nèi)存布局

      Java對(duì)象在堆內(nèi)存中的存儲(chǔ)布局可以分為三個(gè)連續(xù)的區(qū)域:

      1. 對(duì)象頭
      2. 實(shí)例數(shù)據(jù)
      3. 對(duì)齊填充

      2.1 對(duì)象頭

      對(duì)象頭包含了JVM用于管理對(duì)象所必需的運(yùn)行時(shí)數(shù)據(jù)。它本身又由三部分組成:Mark Word類(lèi)型指針,以及數(shù)組長(zhǎng)度(如果是數(shù)組對(duì)象)。

      a) Mark Word

      這是對(duì)象頭中最重要的部分,用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)。它的長(zhǎng)度在32位JVM上是32位,在64位JVM上是64位。

      為了實(shí)現(xiàn)空間高效利用,Mark Word的設(shè)計(jì)是非固定的,會(huì)根據(jù)對(duì)象的狀態(tài)在不同時(shí)刻存儲(chǔ)不同的內(nèi)容,以復(fù)用自己的存儲(chǔ)空間。下表展示了在64位JVM下,Mark Word在不同狀態(tài)下的存儲(chǔ)內(nèi)容:

      鎖狀態(tài) (Lock State) 25 bits (64位JVM) 31 bits (64位JVM) 1 bit (cms_free) 4 bits (分代年齡) 1 bit (偏向鎖標(biāo)志) 2 bits (鎖標(biāo)志位)
      無(wú)鎖 (Unlocked) unused identity_hashcode 分代年齡 (age) 0 01
      偏向鎖 (Biased) thread_id (54 bits) epoch (2 bits) 分代年齡 (age) 1 01
      輕量級(jí)鎖 (Lightweight Locked) 指向棧中鎖記錄的指針 (ptr_to_lock_record) 00
      重量級(jí)鎖 (Heavyweight Locked) 指向監(jiān)視器(管程/互斥量)的指針 (ptr_to_heavyweight_monitor) 10
      GC標(biāo)記 (Marked for GC) unused 11
      • 身份哈希碼:調(diào)用Object.hashCode()System.identityHashCode()后計(jì)算并存儲(chǔ)的結(jié)果。一旦存入,該值會(huì)一直伴隨該對(duì)象
      • 分代年齡:對(duì)象在Survivor區(qū)每熬過(guò)一次Minor GC,年齡就增加1。此值有4位,最大為15,這就是為什么-XX:MaxTenuringThreshold的默認(rèn)最大值是15
      • 鎖信息:synchronized鎖的等級(jí)(偏向鎖、輕量級(jí)鎖、重量級(jí)鎖)相關(guān)的線程ID、鎖記錄指針、重量級(jí)鎖監(jiān)視器指針等
      • GC狀態(tài):標(biāo)記該對(duì)象是否被垃圾回收器標(biāo)記為可回收狀態(tài)

      b) 類(lèi)型指針

      • 即對(duì)象指向它的類(lèi)型元數(shù)據(jù)的指針
      • JVM通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例
      • 并不是所有JVM實(shí)現(xiàn)都必須在對(duì)象數(shù)據(jù)上保留類(lèi)型指針(這取決于對(duì)象的訪問(wèn)定位方式,例如使用句柄池就不需要),但通過(guò)直接指針(HotSpot默認(rèn)方式)訪問(wèn)則必須存在
      • 在64位JVM上,如果開(kāi)啟了壓縮指針(-XX:+UseCompressedOops,默認(rèn)開(kāi)啟),該指針的長(zhǎng)度為4字節(jié)(32位),否則為8字節(jié)(64位)

      c) 數(shù)組長(zhǎng)度

      • 只有數(shù)組對(duì)象才有
      • 用于記錄數(shù)組的長(zhǎng)度,占4字節(jié)(32位)
      • 有了這個(gè)字段,JVM就可以從數(shù)組對(duì)象的元數(shù)據(jù)中確定數(shù)組的大小,而不需要必須通過(guò)類(lèi)型元數(shù)據(jù)

      對(duì)象頭大小總結(jié):

      • 在64位JVM(開(kāi)啟壓縮指針)下
        • 普通對(duì)象:Mark Word(8字節(jié)) + 類(lèi)型指針(4字節(jié)) = 12字節(jié)
        • 數(shù)組對(duì)象:Mark Word(8字節(jié)) + 類(lèi)型指針(4字節(jié)) + 數(shù)組長(zhǎng)度(4字節(jié)) = 16字節(jié)
      • 在64位JVM(關(guān)閉壓縮指針)下
        • 普通對(duì)象:Mark Word(8字節(jié)) + 類(lèi)型指針(8字節(jié)) = 16字節(jié)
        • 數(shù)組對(duì)象:Mark Word(8字節(jié)) + 類(lèi)型指針(8字節(jié)) + 數(shù)組長(zhǎng)度(4字節(jié)) + 對(duì)齊填充(4字節(jié)) = 24字節(jié)

      2.2 實(shí)例數(shù)據(jù)

      • 這是對(duì)象真正存儲(chǔ)的有效信息,即我們?cè)诔绦虼a里所定義的各種類(lèi)型的字段內(nèi)容,無(wú)論是從父類(lèi)繼承下來(lái)的,還是在子類(lèi)中定義的,都必須記錄起來(lái)
      • 存儲(chǔ)順序:JVM默認(rèn)會(huì)按照以下規(guī)則對(duì)字段進(jìn)行排序:
        1. 父類(lèi)定義的變量會(huì)出現(xiàn)在子類(lèi)之前
        2. 較寬的變量(如double/long)通常會(huì)被分配在更靠前的位置(但HotSpot VM較新版本的一些優(yōu)化策略可能會(huì)有所調(diào)整)
        3. 如果開(kāi)啟了-XX:CompactFields參數(shù)(默認(rèn)開(kāi)啟),JVM允許子類(lèi)中較窄的變量插入到父類(lèi)變量的空隙中,以節(jié)省空間
      • 這部分的大小完全由類(lèi)的字段定義決定

      2.3 對(duì)齊填充

      • 這部分不是必須存在的,僅僅起著占位符的作用
      • 目的:HotSpot VM的自動(dòng)內(nèi)存管理系統(tǒng)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。換句話說(shuō),就是任何對(duì)象的大小都必須是8字節(jié)的整數(shù)倍
      • 對(duì)象頭和實(shí)例數(shù)據(jù)部分結(jié)束后,如果整個(gè)對(duì)象的大小還不是8字節(jié)的整數(shù)倍,那么對(duì)齊填充就會(huì)發(fā)揮作用,將剩余的空間補(bǔ)足
      • 這完全是出于性能考慮,讓CPU讀取數(shù)據(jù)更加高效(例如,一次總線事務(wù)可以讀取完整的數(shù)據(jù))

      對(duì)象內(nèi)存布局可視化:

      graph TD A["對(duì)象頭 (Header)"] --> B["實(shí)例數(shù)據(jù) (Instance Data)"] B --> C["對(duì)齊填充 (Padding)"]

      2.4 實(shí)戰(zhàn):使用JOL分析對(duì)象布局

      OpenJDK提供了Java Object Layout (JOL) 工具包,可以讓我們直觀地查看對(duì)象的內(nèi)存布局。

      示例代碼:

      首先引入JOL庫(kù)(如果使用Maven):

      <dependency>
          <groupId>org.openjdk.jol</groupId>
          <artifactId>jol-core</artifactId>
          <version>0.17</version>
          <scope>provided</scope>
      </dependency>
      

      然后編寫(xiě)分析代碼:

      import org.openjdk.jol.info.ClassLayout;
      import org.openjdk.jol.vm.VM;
      
      public class ObjectLayoutDemo {
          public static void main(String[] args) {
              // 打印JVM詳情(例如是否開(kāi)啟壓縮指針)
              System.out.println(VM.current().details());
      
              // 分析一個(gè)簡(jiǎn)單的對(duì)象
              Object obj = new Object();
              System.out.println(ClassLayout.parseInstance(obj).toPrintable());
      
              // 分析一個(gè)自定義對(duì)象
              class MyClass {
                  private int id;
                  private String name;
                  private boolean flag;
                  // 未使用的填充空間可能會(huì)被JVM優(yōu)化
              }
      
              MyClass myObj = new MyClass();
              System.out.println(ClassLayout.parseInstance(myObj).toPrintable());
          }
      }
      

      可能的輸出(在64位JVM,開(kāi)啟壓縮指針下):

      # Running 64-bit HotSpot VM.
      # Using compressed oop with 3-bit shift.
      # Using compressed klass with 3-bit shift.
      
      java.lang.Object object internals:
       OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
            0     4        (object header)                           01 00 00 00 # (Mark Word: 無(wú)鎖狀態(tài),identity_hashcode未計(jì)算)
            4     4        (object header)                           00 00 00 00 # (Mark Word 繼續(xù))
            8     4        (object header)                           e5 01 00 f8 # (類(lèi)型指針,壓縮后4字節(jié))
           12     4        (loss due to the next object alignment)   <-- 對(duì)齊填充
      Instance size: 16 bytes
      Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
      
      ObjectLayoutDemo$1MyClass object internals:
       OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
            0     4                    (object header)                           01 00 00 00
            4     4                    (object header)                           00 00 00 00
            8     4                    (object header)                           77 1b 01 f8 # 類(lèi)型指針
           12     4                int MyClass.id                                0
           16     1            boolean MyClass.flag                              false
           17     3                    (alignment/padding gap)                  # 為了將后面的引用對(duì)齊到8字節(jié)而做的填充
           20     4   java.lang.String MyClass.name                              null
           24     4                    (loss due to the next object alignment)   # 對(duì)象總大小24字節(jié),需要填充到8的倍數(shù)(24已是8的倍數(shù),這里可能無(wú)填充或顯示0)
      Instance size: 24 bytes
      Space losses: 3 bytes internal + 0 bytes external = 3 bytes total
      

      從輸出中可以清晰地看到:

      1. Object對(duì)象:12字節(jié)對(duì)象頭 + 4字節(jié)填充 = 16字節(jié)
      2. MyClass對(duì)象:12字節(jié)對(duì)象頭 + 4字節(jié)(int) + 1字節(jié)(boolean) + 3字節(jié)(對(duì)齊間隙) + 4字節(jié)(壓縮后的引用) = 24字節(jié)。內(nèi)部的3字節(jié)填充是為了讓name引用字段從20字節(jié)開(kāi)始(不是8的倍數(shù)),跳到24字節(jié)(是8的倍數(shù)),這樣CPU訪問(wèn)效率更高

      三、對(duì)象的訪問(wèn)定位

      對(duì)象的訪問(wèn)定位探討的是這樣一個(gè)核心問(wèn)題:棧上的reference類(lèi)型數(shù)據(jù)(即我們通常所說(shuō)的"對(duì)象引用"或"指針")如何準(zhǔn)確地定位到堆中對(duì)象實(shí)例的具體位置?

      JVM規(guī)范只規(guī)定了reference類(lèi)型是一個(gè)指向?qū)ο蟮囊茫⑽炊x這個(gè)引用應(yīng)該通過(guò)何種方式去定位、訪問(wèn)堆中對(duì)象的具體位置。主流的JVM實(shí)現(xiàn)主要有兩種方式:

      1. 使用句柄訪問(wèn)
      2. 使用直接指針訪問(wèn)

      HotSpot VM主要采用第二種方式,但理解第一種方式對(duì)于對(duì)比和深入理解至關(guān)重要。

      3.1 使用句柄訪問(wèn)

      如果使用句柄方式,Java堆將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池

      • reference中存儲(chǔ)的是對(duì)象的句柄地址
      • 句柄本身包含了對(duì)象實(shí)例數(shù)據(jù)的指針和對(duì)象類(lèi)型數(shù)據(jù)的指針

      其內(nèi)存結(jié)構(gòu)和訪問(wèn)過(guò)程如下圖所示:

      flowchart TD A[棧 Stack<br>reference變量] --> B["存儲(chǔ): 句柄地址"] B --> C[訪問(wèn)句柄池] subgraph C[Java堆 Heap: 句柄池] direction LR D[句柄] end subgraph D direction TB E[實(shí)例數(shù)據(jù)指針] --> F[Java堆 Heap: 實(shí)例池<br>對(duì)象實(shí)例數(shù)據(jù)<br>(如: instance_var=100)] H[類(lèi)型數(shù)據(jù)指針] --> I[方法區(qū) Method Area<br>對(duì)象類(lèi)型數(shù)據(jù)<br>(如: 類(lèi)信息、常量、靜態(tài)變量等)] end

      優(yōu)點(diǎn):

      • 引用穩(wěn)定reference本身存儲(chǔ)的是穩(wěn)定的句柄地址。當(dāng)對(duì)象被垃圾收集器移動(dòng)時(shí)(例如在標(biāo)記-壓縮或復(fù)制算法中),只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要做任何修改。這對(duì)于那些需要頻繁進(jìn)行GC優(yōu)化(如壓縮堆)的場(chǎng)景非常友好

      缺點(diǎn):

      • 訪問(wèn)速度相對(duì)較慢:訪問(wèn)對(duì)象實(shí)例數(shù)據(jù)需要兩次指針定位(先定位到句柄,再通過(guò)句柄定位到實(shí)例數(shù)據(jù)),這比直接指針訪問(wèn)多了一次內(nèi)存尋址開(kāi)銷(xiāo)。而對(duì)象訪問(wèn)在Java程序中是非常頻繁的操作,因此這種開(kāi)銷(xiāo)會(huì)被放大

      3.2 使用直接指針訪問(wèn)(HotSpot采用的方式)

      如果使用直接指針?lè)绞剑敲碕ava堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類(lèi)型數(shù)據(jù)的相關(guān)信息。

      • reference中存儲(chǔ)的就是對(duì)象的直接地址
      • 對(duì)象實(shí)例數(shù)據(jù)中需要包含一個(gè)指向方法區(qū)中對(duì)象類(lèi)型數(shù)據(jù)的指針(即對(duì)象頭中的類(lèi)型指針

      其內(nèi)存結(jié)構(gòu)和訪問(wèn)過(guò)程如下圖所示:

      flowchart TD A[棧 Stack<br>reference變量] --> B["存儲(chǔ): 對(duì)象直接地址"] B --> C subgraph C[Java堆 Heap: 實(shí)例數(shù)據(jù)] direction TB D[對(duì)象頭<br>類(lèi)型指針] --> E[方法區(qū) Method Area<br>對(duì)象類(lèi)型數(shù)據(jù)] F[對(duì)象實(shí)例數(shù)據(jù)<br>(如: instance_var=100)] end

      優(yōu)點(diǎn):

      • 訪問(wèn)速度更快:相比于句柄方式,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)。由于對(duì)象訪問(wèn)是Java程序中最頻繁的操作之一,因此這類(lèi)開(kāi)銷(xiāo)的減少對(duì)性能的提升是非常可觀的

      缺點(diǎn):

      • 引用不穩(wěn)定:當(dāng)對(duì)象被移動(dòng)時(shí)(例如GC后內(nèi)存整理),reference本身存儲(chǔ)的地址需要被直接更新。如果有很多地方都引用了這個(gè)對(duì)象,更新這些引用的開(kāi)銷(xiāo)會(huì)比較大(不過(guò)現(xiàn)代GC算法如Shenandoah、ZGC等,通過(guò)讀屏障等技術(shù)極大地優(yōu)化了這個(gè)問(wèn)題)

      3.3 HotSpot VM的實(shí)現(xiàn)與優(yōu)化

      HotSpot VM主要使用直接指針?lè)绞竭M(jìn)行對(duì)象訪問(wèn)。

      你可能會(huì)問(wèn),它如何解決直接指針的"引用不穩(wěn)定"這個(gè)缺點(diǎn)呢?答案是:通過(guò)復(fù)雜的GC算法和精巧的實(shí)現(xiàn)來(lái)協(xié)同解決

      1. 針對(duì)移動(dòng)對(duì)象的處理

        • 在發(fā)生垃圾回收,特別是需要壓縮堆(如使用Serial, ParNew, G1等收集器的老年代回收)時(shí),HotSpot確實(shí)需要更新所有指向被移動(dòng)對(duì)象的引用
        • 這個(gè)過(guò)程是由GC器通過(guò)記憶集卡表等技術(shù)來(lái)跟蹤哪些引用需要更新,并在STW階段高效地完成所有引用的更新操作。雖然這增加了GC的復(fù)雜性,但換取的是運(yùn)行時(shí)更高的訪問(wèn)性能
      2. 壓縮指針

        • 在64位JVM上,直接指針的大小是64位(8字節(jié)),這相比32位指針會(huì)帶來(lái)更大的內(nèi)存占用和帶寬消耗
        • HotSpot引入了壓縮指針技術(shù)(-XX:+UseCompressedOops,默認(rèn)開(kāi)啟)
        • 原理:并非真的用64位地址去尋址,而是通過(guò)一定的偏移和縮放,將64位的地址用32位的數(shù)據(jù)來(lái)存儲(chǔ)和表示。JVM在運(yùn)行時(shí)會(huì)將這32位的"壓縮指針"左移3位(相當(dāng)于乘以8)再加上一個(gè)基地址,來(lái)得到真正的64位地址
        • 效果:這讓引用變量的大小從8字節(jié)降到了4字節(jié),節(jié)省了大量?jī)?nèi)存(尤其是大量小對(duì)象時(shí)),同時(shí)因?yàn)镃PU緩存能容納更多引用,也間接提升了訪問(wèn)速度

      3.4 對(duì)比總結(jié)

      特性 句柄訪問(wèn) 直接指針訪問(wèn) (HotSpot)
      reference存儲(chǔ)內(nèi)容 句柄的地址 對(duì)象的直接地址
      訪問(wèn)開(kāi)銷(xiāo) 兩次指針尋址(速度慢) 一次指針尋址(速度快)
      GC時(shí)引用更新 對(duì)象移動(dòng)時(shí),只需更新句柄,reference不變 對(duì)象移動(dòng)時(shí),必須更新reference
      內(nèi)存占用 需要額外句柄池空間 對(duì)象頭中需要類(lèi)型指針,但總體更節(jié)省
      優(yōu)點(diǎn) 引用穩(wěn)定,利于GC 性能極高,訪問(wèn)速度快
      缺點(diǎn) 訪問(wèn)速度慢,占用額外內(nèi)存 GC時(shí)更新引用的開(kāi)銷(xiāo)更大

      四、總結(jié)

      Java對(duì)象的創(chuàng)建、內(nèi)存布局和訪問(wèn)定位是JVM的核心機(jī)制。通過(guò)本文的詳細(xì)解析,我們可以看到,一個(gè)簡(jiǎn)單的new操作背后,JVM進(jìn)行了類(lèi)加載檢查、內(nèi)存分配、初始化、設(shè)置對(duì)象頭和執(zhí)行構(gòu)造方法等一系列復(fù)雜操作。

      對(duì)象在內(nèi)存中的布局分為對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充三部分,其中對(duì)象頭包含了Mark Word和類(lèi)型指針等關(guān)鍵信息,用于JVM管理對(duì)象的生命周期、同步狀態(tài)和類(lèi)型識(shí)別。

      HotSpot VM為了追求極致的執(zhí)行性能,選擇了直接指針作為對(duì)象訪問(wèn)定位的方式。它通過(guò)投入巨大的工程復(fù)雜度在垃圾收集器上(如精確式GC、卡表、讀屏障等技術(shù))來(lái)克服直接指針在對(duì)象移動(dòng)時(shí)的缺點(diǎn),從而最終贏得了性能上的優(yōu)勢(shì)。

      理解這些底層機(jī)制,不僅能夠幫助我們編寫(xiě)更高效的Java代碼,還能在進(jìn)行性能調(diào)優(yōu)、內(nèi)存分析和故障排查時(shí)提供重要的理論依據(jù)和實(shí)踐指導(dǎo)。

      附錄

      相關(guān)JVM參數(shù)

      • -XX:+UseTLAB:?jiǎn)⒂镁€程本地分配緩沖(默認(rèn)開(kāi)啟)
      • -XX:+UseCompressedOops:?jiǎn)⒂脡嚎s指針(64位JVM默認(rèn)開(kāi)啟)
      • -XX:CompactFields:允許子類(lèi)窄變量插入父類(lèi)變量空隙(默認(rèn)開(kāi)啟)
      • -XX:MaxTenuringThreshold:設(shè)置對(duì)象晉升老年代的年齡閾值(默認(rèn)15)

      推薦工具

      • JOL(Java Object Layout):分析對(duì)象內(nèi)存布局
      • HSDB(HotSpot Debugger):深入分析JVM運(yùn)行時(shí)狀態(tài)
      • JMC(Java Mission Control):監(jiān)控和分析JVM運(yùn)行性能

      參考資料

      1. 《深入理解Java虛擬機(jī)》 - 周志明
      2. OpenJDK官方文檔
      3. Java語(yǔ)言規(guī)范(JLS)
      4. JVM規(guī)范(JVMS)
      5. Oracle官方Java性能調(diào)優(yōu)指南
      posted @ 2025-09-11 15:21  佛祖讓我來(lái)巡山  閱讀(266)  評(píng)論(0)    收藏  舉報(bào)

      佛祖讓我來(lái)巡山博客站 - 創(chuàng)建于 2018-08-15

      開(kāi)發(fā)工程師個(gè)人站,內(nèi)容主要是網(wǎng)站開(kāi)發(fā)方面的技術(shù)文章,大部分來(lái)自學(xué)習(xí)或工作,部分來(lái)源于網(wǎng)絡(luò),希望對(duì)大家有所幫助。

      Bootstrap中文網(wǎng)

      主站蜘蛛池模板: 欧洲国产成人久久精品综合| 亚洲精品99久久久久久欧美版| 日韩一区二区三区在线视频| 美女视频黄频大全视频| 少妇愉情理伦片丰满丰满午夜| 亚洲悠悠色综合中文字幕| 日韩不卡二区三区三区四区| 欧美黑人添添高潮a片www| 99精品热在线在线观看视| 亚洲女同性同志熟女| 成人国产精品一区二区网站公司| 日韩av无码中文无码电影| 东京热无码国产精品| 亚洲暴爽av天天爽日日碰| 激情综合网激情国产av| 阿瓦提县| 国产一区一一区高清不卡| 国产一区二区日韩经典| 三上悠亚在线精品二区| 日本一区三区高清视频| 99久久成人亚洲精品观看| 亚洲国产成人无码av在线影院| 午夜在线观看成人av| 亚洲成av人片天堂网无码 | 亚洲日本欧美日韩中文字幕| 男女性高爱潮免费网站| 亚洲香蕉伊综合在人在线| 国产极品粉嫩尤物一线天| 久久精品国产热久久精品国产亚洲| 大陆精大陆国产国语精品| 国产h视频在线观看| 怡春院欧美一区二区三区免费| 中文字幕理伦午夜福利片| 亚洲av无码一区二区三区网站| 卡一卡2卡3卡精品网站| 修文县| 91老肥熟女九色老女人| 依依成人精品视频在线观看| 农民人伦一区二区三区| 亚洲精品人成网线在线播放va| 亚洲真人无码永久在线|