对象内存布局

了解对象在内存中的存储结构

🎯 面试重点

📖 对象内存布局

对象结构

/**
 * Java 对象在内存中的布局:
 * 
 * ┌─────────────────────────────────────┐
 * │           对象头(Header)           │
 * │  ┌───────────────────────────────┐  │
 * │  │      Mark Word(标记字段)     │  │  8字节 (64位)
 * │  │  (hashcode, age, lock, etc)    │  │
 * │  ├───────────────────────────────┤  │
 * │  │      Klass Pointer(类指针)   │  │  4/8字节
 * │  │  (指向类元数据的指针)          │  │
 * │  ├───────────────────────────────┤  │
 * │  │   数组长度(仅数组对象有)     │  │  4字节
 * │  └───────────────────────────────┘  │
 * ├─────────────────────────────────────┤
 * │         实例数据(Instance Data)    │
 * │   父类字段 + 自身字段                │
 * ├─────────────────────────────────────┤
 * │          对齐填充(Padding)         │
 * └─────────────────────────────────────┘
 */
public class ObjectLayout {}

对象头详解

/**
 * Mark Word(标记字段)
 * 64位 JVM 下占 8 字节,存储对象自身运行时数据:
 */
public class MarkWordDemo {
    /*
     * 对象未锁定状态:
     * 25bit hashcode + 2bit age + 1bit lock(01) + 4bit unused = 64bit
     *
     *轻量级锁定状态:
     * 62bit pointer to lock record + 2bit lock(00) = 64bit
     *
     *重量级锁定状态:
     * 62bit pointer to monitor + 2bit lock(10) = 64bit
     *
     *GC 标记状态:
     * 62bit unused + 2bit lock(11) = 64bit
     */
}

/**
 * Klass Pointer(类指针)
 * - 指向方法区中类元数据的指针
 * - 通过这个指针确定对象是哪个类的实例
 * - 开启了指针压缩(-XX:+UseCompressedClassPointers)时为 4 字节,否则 8 字节
 */
public class KlassPointerDemo {
    // Class 对象在堆中,类元数据在方法区
    // Object obj = new Object();
    // obj.getClass() 通过 Klass Pointer 找到 Class 对象
}

实例数据

/**
 * 实例数据存放规则:
 * 1. 父类变量排在子类之前
 * 2. 相同宽度的字段放在一起
 * 3. 成员变量按照声明顺序排放
 */
public class InstanceData {
    // 示例
    class Parent {
        long l;     // 8字节
        int i;      // 4字节
    }
    
    class Child extends Parent {
        String s;   // 4/8字节(引用)
        int j;      // 4字节
        double d;   // 8字节
        byte b;     // 1字节
    }
    
    // 实际顺序(考虑对齐):
    // l (8) + i (4) + 空白(4) = 16
    // s (4/8) + j (4) + d (8) + b (1) + 空白(7) = 32/40
}

对齐填充

/**
 * 对齐填充(Padding)
 * 
 * 原因:CPU 访问内存时以字(word)为单位
 *      不是对齐的数据可能需要多次访问
 * 
 * 规则:对象大小必须是 8 字节的整数倍
 *      不够则填充
 */
public class PaddingDemo {
    // 示例
    /*
     * class A {
     *     byte b;    // 1字节
     * }
     * 
     * 实际占用:1 + 7(填充) = 8字节
     * 
     * class B {
     *     long l;    // 8字节
     *     byte b;    // 1字节
     * }
     * 
     * 实际占用:8 + 1 + 7(填充) = 16字节
     */
}

验证方法

/**
 * 使用 JOL (Java Object Layout) 验证
 */
public class JOLTest {
    // 添加依赖
    // <dependency>
    //     <groupId>org.openjdk.jol</groupId>
    //     <artifactId>jol-core</artifactId>
    //     <version>0.17</version>
    // </dependency>
    
    public static void main(String[] args) {
        // 查看对象布局
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
        
        // 查看数组布局
        System.out.println(ClassLayout.parseInstance(new int[0]).toPrintable());
        
        // 查看带锁对象布局
        synchronized (new Object()) {
            System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
        }
    }
}

📖 面试真题

Q1: new Object() 占多少字节?

答:

Q2: new Object[10] 占多少字节?

答:

Q3: 对象头中的锁状态?

答:


⭐ 重点:对象内存布局是理解 JVM 的基础,面试中经常结合 OOM 问题考察