首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

JVM 深度解析:Java 對象內存佈局與大小計算實戰(含 Instrumentation Agent 代碼)

     編輯:Java 綜合教學

JVM 深度解析:Java 對象內存佈局與大小計算實戰(含 Instrumentation Agent 代碼)

為什麼需要計算對象大小?

  • 堆外緩存設計:預估緩存條目佔用,配合 LRU 淘汰策略防止 OOM。
  • JVM 參數調優:根據對象數量與大小推算 -Xmx、-Xms、新生代/老年代比例。
  • 性能調優基線:識別大對象、內存洩漏疑點,指導壓縮指針、逃逸分析優化。

HotSpot 對象內存佈局(64 位 JVM,開啟壓縮指針 -XX:+UseCompressedOops)

+------------------+------------------+------------------+
|   Object Header  |  Instance Data   |    Padding       |
|   (12 bytes)     |   (Fields)       |   (0-7 bytes)    |
+------------------+------------------+------------------+
對齊要求:總大小為 8 字節倍數

1. 對象頭

  • Mark Word (8 bytes):哈希碼、GC 分代年齡、鎖狀態標誌、偏向鎖線程 ID 等。
  • Klass Pointer (4 bytes):指向類元數據的指針(壓縮指針模式下 4 字節,關閉則 8 字節)。
  • 數組長度 (4 bytes):僅數組對象具備,緊隨 Klass Pointer 之後。

普通對象頭 = 12 bytes;數組對象頭 = 16 bytes(開啟壓縮指針)。

2. 實例數據

類型大小備註
boolean, byte1 byte
char, short2 bytes
int, float4 bytes
long, double8 bytes
reference (對象引用)4 bytes開啟壓縮指針;關閉則 8 bytes
  • 字段按「長類型優先、父類優先、寬度對齊」原則排列,JVM 會自動重排以減少 Padding。
  • static 字段不計入實例大小(屬於類元數據區)。

3. 對齊填充

對象起始地址 8 字節對齊,總大小補齊至 8 的倍數。Padding = (8 - (Header + Data) % 8) % 8。

實戰:基於 Instrumentation Agent 精確測量

Agent 核心代碼

// SizeOfAgent.java
package com.example.jvm;

import java.lang.instrument.Instrumentation;

public class SizeOfAgent {
    private static Instrumentation inst;

    public static void premain(String args, Instrumentation instP) {
        inst = instP;
    }

    // 淺層大小(不含引用對象)
    public static long sizeOf(Object o) {
        if (inst == null) throw new IllegalStateException("Agent not initialized");
        return inst.getObjectSize(o);
    }

    // 深層大小(遞歸計算引用鏈,簡易版)
    public static long fullSizeOf(Object o) {
        // 完整實現需配合反射遍歷字段、處理循環引用、數組展開,此處略
        return sizeOf(o); // 佔位
    }
}

MANIFEST.MF 關鍵屬性

Manifest-Version: 1.0
Premain-Class: com.example.jvm.SizeOfAgent
Can-Redefine-Classes: false
Can-Retransform-Classes: false

Maven 打包配置

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Premain-Class>com.example.jvm.SizeOfAgent</Premain-Class>
                <Can-Redefine-Classes>false</Can-Redefine-Classes>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

運行參數

java -javaagent:target/sizeof-agent.jar -XX:+UseCompressedOops -cp target/classes com.example.jvm.SizeOfAgentTest

測試用例與結果分析(64 位,壓縮指針開啟)

public class SizeOfAgentTest {
    static class A { int a; Integer b; }
    static class B { int a; Integer b; }
    static class C { int c; B[] b = new B[2]; { b[0]=new B(); b[1]=new B(); } }
    static class D { byte d1; }
    static class E extends D { byte e1; }

    public static void main(String[] args) {
        System.out.println("=== 基礎對象 ===");
        System.out.println("new Object()          : " + SizeOfAgent.sizeOf(new Object()));           // 16
        System.out.println("new A()               : " + SizeOfAgent.sizeOf(new A()));               // 24 (header12 + int4 + ref4 + pad4)
        System.out.println("new Integer(1)        : " + SizeOfAgent.sizeOf(new Integer(1)));        // 24
        System.out.println("new String()          : " + SizeOfAgent.sizeOf(new String()));          // 32
        System.out.println("new String(\"a\")       : " + SizeOfAgent.sizeOf(new String("a")));       // 32

        System.out.println("\n=== 數組對象 ===");
        System.out.println("new byte[0]           : " + SizeOfAgent.sizeOf(new byte[0]));           // 16 (header16)
        System.out.println("new byte[1]           : " + SizeOfAgent.sizeOf(new byte[1]));           // 24 (header16 + 1 + pad7)
        System.out.println("new byte[9]           : " + SizeOfAgent.sizeOf(new byte[9]));           // 32 (header16 + 9 + pad7)
        System.out.println("new Integer[0]        : " + SizeOfAgent.sizeOf(new Integer[0]));        // 16
        System.out.println("new Integer[3]        : " + SizeOfAgent.sizeOf(new Integer[3]));        // 32 (header16 + 3*4 + pad4)

        System.out.println("\n=== 繼承與復合 ===");
        System.out.println("new D()               : " + SizeOfAgent.sizeOf(new D()));               // 16 (header12 + byte1 + pad3)
        System.out.println("new E()               : " + SizeOfAgent.sizeOf(new E()));               // 24 (header12 + byte1(pad3) + byte1 + pad2)
        System.out.println("new C()               : " + SizeOfAgent.sizeOf(new C()));               // 32 (淺層:header12 + int4 + ref4 + pad4)
    }
}

結果解讀關鍵點

  1. new Object() = 16:Header 12 + Padding 4 = 16(對齊)。
  2. new A() = 24:Header 12 + int(4) + ref(4) = 20 → Padding 4 = 24。
  3. Integer 內部 int value + Header = 16,但對象引用指向它佔 4 字節。
  4. 數組對象頭 16 bytes(含長度字段),元素緊湊存儲。
  5. 繼承鏈:父類字段優先佈局,各自遵循對齊規則。

生產環境替代方案

  • JOL (Java Object Layout):org.openjdk.jol:jol-core,提供 ClassLayout.parseClass(Foo.class).toPrintable() 詳細佈局輸出,無需 Agent。
  • Arthas:heapdump + jhat/MAT 離線分析。
  • JVM 參數:-XX:+PrintGCDetails -XX:+PrintHeapAtGC 間接推算。

總結

Java 對象內存佈局遵循確定性規則:Header + Instance Data + Padding。掌握壓縮指針、字段重排、對齊填充原理,配合 JOL 或 Instrumentation Agent 工具化測量,能將內存規劃從「經驗拍腦袋」轉為「數據驅動」,是 JVM 調優進階的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved