知识模块
☕ Java 知识模块
四、JVM 深入理解
对象布局

对象布局

面试提问

"一个 Java 对象在内存中是如何存储的?对象头包含哪些信息?"


核心概念

在 HotSpot JVM 中,Java 对象在堆内存中的布局分为三部分:

┌─────────────────────────────────────────────┐
│              对象头(Object Header)          │
│  ├─ Mark Word(8 字节)                      │
│  └─ 类型指针(4/8 字节)                     │
├─────────────────────────────────────────────┤
│              实例数据(Instance Data)        │
│  └─ 字段数据(各类型占用不同)                 │
├─────────────────────────────────────────────┤
│              对齐填充(Padding)              │
│  └─ 保证对象大小是 8 字节的倍数                │
└─────────────────────────────────────────────┘

对象头详解

1. Mark Word(标记字)

Mark Word 是对象头的核心,大小为 8 字节(64 位 JVM),存储对象运行时信息。

结构(64 位 JVM,开启指针压缩)

┌─────────────────────────────────────────────────────────────┐
│                    Mark Word (64 bits)                      │
├─────────────────────────────────────────────────────────────┤
│  无锁状态:                                                   │
│  │ unused:25 │ hashcode:31 │ unused:1 │ age:4 │ biased:1 │ lock:2 │
├─────────────────────────────────────────────────────────────┤
│  偏向锁状态:                                                 │
│  │ thread:54 │ epoch:2 │ unused:1 │ age:4 │ biased:1 │ lock:2 │
├─────────────────────────────────────────────────────────────┤
│  轻量级锁状态:                                               │
│  │ ptr_to_lock_record:62 │ lock:2 │                           │
├─────────────────────────────────────────────────────────────┤
│  重量级锁状态:                                               │
│  │ ptr_to_heavyweight_monitor:62 │ lock:2 │                   │
├─────────────────────────────────────────────────────────────┤
│  GC 标记状态:                                                │
│  │ unused:62 │ lock:2 │ (11)                                  │
└─────────────────────────────────────────────────────────────┘

关键字段说明

字段大小说明
hashcode31 bits对象哈希码(延迟计算)
age4 bits对象年龄(GC 分代年龄,最大 15)
biased1 bit偏向锁标志
lock2 bits锁状态(01 无锁/偏向,00 轻量级,10 重量级,11 GC 标记)
thread54 bits偏向线程 ID
epoch2 bits偏向时间戳

锁状态对应表

lockbiased状态
010无锁
011偏向锁
00-轻量级锁
10-重量级锁
11-GC 标记

2. 类型指针(Class Pointer)

指向方法区中对象的类元数据,用于确定对象是哪个类的实例。

  • 大小:4 字节(开启指针压缩)或 8 字节(未开启)
  • 数组对象额外有 4 字节存储数组长度
# 开启指针压缩(JDK8 默认开启,堆 < 32G 时有效)
-XX:+UseCompressedOops
 
# 压缩类指针
-XX:+UseCompressedClassPointers

实例数据

存储对象中定义的字段数据,包括父类继承的字段。

字段类型占用空间

类型占用空间
boolean1 字节
byte1 字节
char2 字节
short2 字节
int / float4 字节
long / double8 字节
引用类型4 字节(压缩指针)/ 8 字节

字段重排序: JVM 会对字段进行重排序,让长短不一的字段紧凑排列,减少内存占用。

示例

public class Example {
    boolean b;  // 1 字节
    int i;      // 4 字节
    long l;     // 8 字节
    char c;     // 2 字节
}

内存布局可能是:long(8) + int(4) + char(2) + boolean(1) + padding(1) = 16 字节


对齐填充

确保对象大小是 8 字节的倍数,便于内存管理和 GC。

原因

  • 内存对齐,提高访问效率
  • 方便计算对象边界

对象大小计算示例

示例一:空对象

public class Empty {}

内存布局

Mark Word:    8 字节
Class Pointer: 4 字节(压缩指针)
Padding:      4 字节
─────────────────────
总计:         16 字节

示例二:简单对象

public class Simple {
    int id;        // 4 字节
    boolean flag;  // 1 字节
}

内存布局

Mark Word:    8 字节
Class Pointer: 4 字节
id:           4 字节
flag:         1 字节
Padding:      3 字节
─────────────────────
总计:         20 字节 → 对齐后 24 字节

示例三:数组对象

int[] arr = new int[10];

内存布局

Mark Word:    8 字节
Class Pointer: 4 字节
Array Length: 4 字节(数组特有)
Data:         10 * 4 = 40 字节
Padding:      4 字节
─────────────────────
总计:         60 字节 → 对齐后 64 字节

指针压缩

为什么需要指针压缩?

64 位 JVM 中,指针占用 8 字节,比 32 位多一倍,导致:

  • 对象变大,内存占用增加
  • CPU 缓存命中率降低

压缩原理

将 64 位指针压缩为 32 位,通过位移还原:

压缩地址 = 实际地址 / 8
实际地址 = 压缩地址 * 8

限制:只能寻址 4G * 8 = 32G 内存

开启条件

# JDK8 默认开启,堆 < 32G 时有效
-XX:+UseCompressedOops    # 压缩普通对象指针
-XX:+UseCompressedClassPointers  # 压缩类指针

堆 >= 32G 时,指针压缩自动失效


对象大小查看工具

1. JOL(Java Object Layout)

添加依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.16</version>
</dependency>

使用示例:

import org.openjdk.jol.info.ClassLayout;
 
public class JolDemo {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}

输出:

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x00000851
 12   4        (object alignment gap)
Instance size: 16 bytes

2. Instrumentation

import java.lang.instrument.Instrumentation;
 
public class ObjectSizeUtil {
    private static Instrumentation instrumentation;
 
    public static void premain(String args, Instrumentation inst) {
        instrumentation = inst;
    }
 
    public static long sizeOf(Object obj) {
        return instrumentation.getObjectSize(obj);
    }
}

面试要点总结

问题答案要点
对象布局三部分?对象头、实例数据、对齐填充
Mark Word 存储什么?哈希码、分代年龄、锁状态标志、偏向线程 ID
为什么对象大小要 8 字节对齐?内存对齐提高访问效率,方便 GC 管理
指针压缩原理?64 位指针压缩为 32 位,通过位移还原,最多寻址 32G
对象年龄最大是多少?15(4 bits,最大值 2^4 - 1 = 15)

相关题目

  1. 计算一个对象占用的内存大小?
  2. 为什么对象头中的分代年龄最大是 15?
  3. 指针压缩有什么限制?
  4. 数组对象和普通对象的内存布局有什么区别?

参考资料