问题
一个对象的结构是什么样的?
答案
核心概念
Java对象在JVM内存中的结构由三部分组成:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。这个结构设计充分考虑了内存对齐、垃圾回收和性能优化。
对象结构详解
1. 对象头(Object Header)
对象头是对象的关键信息存储区域,占用空间与JVM位数相关:
32位JVM对象头结构:
| Mark Word (4 bytes) | 类型指针 (4 bytes) | 数组长度 (4 bytes,仅数组对象) |
64位JVM对象头结构:
| Mark Word (8 bytes) | 类型指针 (8 bytes) | 数组长度 (4 bytes,仅数组对象) | 对齐填充 (4 bytes) |
Mark Word 结构
Mark Word存储了对象的运行时数据,在不同状态下结构不同:
// 64位JVM Mark Word示例(简化版)
public class MarkWordStructure {
// 无锁状态
// | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
// 偏向锁状态
// | thread_id:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:1 |
// 轻量级锁状态
// | ptr_to_lock_record:62 | lock:2 |
// 重量级锁状态
// | ptr_to_heavyweight_monitor:62 | lock:2 |
// GC标记状态
// | ForwardingAddress:62 | lock:2 |
}
Mark Word包含信息:
- HashCode:对象的哈希码
- GC分代年龄:记录对象在GC中的存活次数(最大15)
- 锁状态标志:无锁、偏向锁、轻量级锁、重量级锁
- 偏向锁线程ID:持有偏向锁的线程标识
- 锁记录指针:轻量级锁时指向栈中锁记录
- 重量级锁指针:重量级锁时指向对象监视器
类型指针(Class Pointer)
指向对象所属类的元数据信息:
public class Person {
private String name;
}
Person person = new Person();
// 对象头的类型指针指向Person.class的元数据
2. 实例数据(Instance Data)
实例数据存储对象的真正有效信息,包括:
字段存储规则:
- 相同宽度的字段:分配在一起
- 父类字段:在子类字段之前
- 内存对齐:long/double等8字节字段要求8字节对齐
public class Example {
// 字段按内存对齐和顺序存储
private byte b1; // 1 byte
private int i; // 4 bytes
private long l; // 8 bytes(需要8字节对齐)
private byte b2; // 1 byte
private double d; // 8 bytes(需要8字节对齐)
private short s; // 2 bytes
}
// 内存布局示例(64位JVM):
// | b1 | padding(3) | i | l | b2 | padding(7) | d | s | padding(6) |
字段重排序优化:
JVM可能对字段进行重排序以优化内存使用:
public class OptimizedLayout {
// JVM可能重排序为:long, double, int, short, byte, byte
private byte b1; // 1 byte
private long l; // 8 bytes
private byte b2; // 1 byte
private double d; // 8 bytes
private int i; // 4 bytes
private short s; // 2 bytes
}
3. 对齐填充(Padding)
对齐填充不是必需的,但用于保证对象大小是8字节的整数倍:
public class SmallObject {
private byte b; // 只占用1字节
}
// 实际内存布局(64位JVM,开启压缩指针):
// | Mark Word (8) | 类型指针 (4) | 实例数据 (1) | 对齐填充 (3) |
// 总计:16 bytes(8字节对齐)
内存布局示例
普通对象内存占用计算
public class MemoryExample {
public static void main(String[] args) {
// 基础对象内存占用
Object obj = new Object();
// 64位JVM + 压缩指针:Mark Word(8) + 类型指针(4) + 对齐填充(4) = 16 bytes
// 自定义对象
class Person {
int age; // 4 bytes
String name; // 4 bytes(引用类型,压缩指针)
}
Person person = new Person();
// 总计:16(对象头) + 4(age) + 4(name) + 4(对齐) = 28 bytes → 32 bytes(8字节对齐)
}
}
数组对象特殊结构
public class ArrayStructure {
public static void main(String[] args) {
int[] array = new int[5];
// 数组对象结构:
// | Mark Word (8) | 类型指针 (4) | 数组长度 (4) | 数据区域 (20) | 对齐填充 (0) |
// 总计:36 bytes → 40 bytes(8字节对齐)
// 对象数组
String[] strArray = new String[5];
// 数据区域:5 * 4 = 20 bytes(引用大小)
// 总计:36 bytes → 40 bytes
}
}
性能优化考虑
1. 内存对齐优化
// 不好的设计:内存碎片
public class BadDesign {
private byte b1;
private long l;
private byte b2;
private double d;
}
// 优化设计:减少内存碎片
public class GoodDesign {
private long l;
private double d;
private byte b1;
private byte b2;
}
2. 字段访问性能
字段在内存中的连续性影响缓存性能:
public class CacheFriendly {
// 经常一起访问的字段放在一起
private int id;
private int count;
private long timestamp;
// 不经常访问的字段放后面
private String description;
private Object metadata;
}
面试要点总结
- 对象三部分:对象头 + 实例数据 + 对齐填充
- 对象头组成:Mark Word + 类型指针 [+ 数组长度]
- Mark Word作用:存储哈希码、GC年龄、锁状态等
- 实例数据规则:按宽度分组、父类在前、内存对齐
- 内存对齐:保证8字节边界,提高访问效率
- 数组特殊:额外存储数组长度信息
理解对象结构有助于:
- 精确计算内存占用
- 优化字段排列提升缓存性能
- 理解锁机制和GC原理
- 分析内存泄漏问题
这是JVM内存管理的基础知识,对性能调优和问题诊断都很重要。