Java对象内存结构详解
Java对象在JVM内存中的存储结构可以分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。以下是64位JVM(开启压缩指针)下的典型布局:
1. 对象头(Header)
对象头包含运行时元数据和控制信息,占12字节(压缩指针)或16字节(未压缩)
(1) Mark Word(8字节)
存储对象自身的运行时数据,内容会随锁状态变化:
锁状态 | 存储内容 |
---|---|
无锁 | 哈希码(31bit) + 分代年龄(4bit) + 偏向模式(1bit) + 锁标志(2bit) |
偏向锁 | 线程ID(54bit) + Epoch(2bit) + 分代年龄(4bit) + 偏向模式(1bit) + 锁标志(2bit) |
轻量级锁 | 指向栈中锁记录的指针(62bit) + 锁标志(2bit) |
重量级锁 | 指向监视器(Monitor)的指针(62bit) + 锁标志(2bit) |
GC标记 | 空(用于垃圾回收标记) |
(2) Klass Pointer(4字节)
指向方法区中的类元数据的指针(开启压缩指针时为4字节,否则8字节)
(3) 数组长度(可选,4字节)
如果是数组对象,额外存储数组长度
2. 实例数据(Instance Data)
存储对象真正的有效信息,即各个字段的内容,排列顺序受以下规则影响:
- 基本类型优先(long/double → int/float → short/char → byte/boolean)
- 相同宽度字段放在一起
- 父类字段在子类之前
字段内存占用:
类型 | 大小 |
---|---|
boolean | 1字节 |
byte | 1字节 |
short | 2字节 |
char | 2字节 |
int | 4字节 |
float | 4字节 |
long | 8字节 |
double | 8字节 |
引用类型 | 4字节(压缩指针)或8字节 |
示例:
class MyObject {byte b; // 1字节int i; // 4字节long l; // 8字节Object ref; // 4字节(压缩指针)
}
// 实例数据总大小 = 1 + 4 + 8 + 4 = 17字节
3. 对齐填充(Padding)
JVM要求对象起始地址必须是8字节的整数倍,因此可能需要填充字节(0-7字节)
示例计算:
对象头:12字节 (MarkWord 8 + KlassPointer 4)
实例数据:17字节
总计:12 + 17 = 29字节
需要填充到32字节(8的倍数)
最终对象大小:32字节
4. 完整对象内存布局示例
[对象头][Mark Word(8字节)] 0x00000001d83eb950[Klass Pointer(4字节)] 0x0000000100000000[数组长度(4字节)] 0x00000005 (仅数组对象有)
[实例数据][int id(4字节)] 0x00000001[String name(4字节)] 0x000000076bdc8c18[double price(8字节)] 0x4024000000000000
[对齐填充(4字节)] 0x00000000
5. 查看对象内存布局的方法
(1) 使用JOL工具
// 添加依赖
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.16</version>
</dependency>// 打印对象布局
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
(2) 示例输出
class com.example.MyObject object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00000001d83eb9508 4 (object header: class) 0x000000010000000012 4 int MyObject.id 116 8 double MyObject.price 10.024 4 java.lang.String MyObject.name "test"28 4 (object alignment gap)
Instance size: 32 bytes
6. 对象结构优化技巧
-
字段重排序:手动排列字段可减少填充
// 优化前:需要4字节填充 class Bad {byte b;long l;int i; } // 优化后:无填充 class Good {long l;int i;byte b; }
-
使用基本类型:避免包装类(Integer等)的内存开销
-
对象共享:对于不变对象可复用(如String池)
资料免费获取