《彩蛋:用HSDB工具偷看对象内存布局 - 当Java对象开始裸奔》
前置准备
# 需要JDK8环境(JDK9+默认禁用SA工具)
# 添加JVM启动参数允许调试
java -XX:+UnlockDiagnosticVMOptions -cp YourClassPath YourMainClass
-
-XX:+UnlockDiagnosticVMOptions:- 这是一个JVM选项,用于解锁诊断工具相关的功能。
- 启用后,可以使用其他诊断工具(如
jstack、jmap等)来分析正在运行的Java进程。
-
-cp YourClassPath:-cp是classpath的缩写,用于指定应用程序所需的类路径。YourClassPath应替换为实际的类路径(例如:./out/production:libs/*)。
-
YourMainClass:- 这是你的Java应用程序的主类(即包含
public static void main(String[] args)方法的类)。 - 替换为实际的主类名称。
- 这是你的Java应用程序的主类(即包含
实战代码
public class ObjectMemoryPeek {
// 故意不对齐的字段布局
private int id = 42;
private boolean flag = true;
private String name = "HSDB侦探";
public static void main(String[] args) throws Exception {
ObjectMemoryPeek target = new ObjectMemoryPeek();
System.out.println("目标对象已创建,对象地址:" + target.hashCode());
System.in.read(); // 在此处阻塞,等待HSDB附加
}
}
侦探行动指南
第一步:启动HSDB(打开你的X光机)
# Windows
jdk8\bin\java.exe -cp jdk8\lib\sa-jdi.jar sun.jvm.hotspot.HSDB
# Linux/Mac
sudo java -cp $JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
操作图解:
- 点击菜单栏
File -> Attach to HotSpot process - 输入刚刚运行的Java进程PID(可用
jps命令查看) - 恭喜你!现在能看到JVM的裸奔现场了
第二步:定位对象地址(给对象装上GPS)
// 代码中打印的hashCode并非真实地址,需要转换
// 在HSDB控制台执行:
hsdb> revptrs 0x00000000e5c9f158 // 替换为你的hashCode
技术原理:
对象头包含 mark word(存储哈希码和锁状态)和 klass pointer(指向类元数据),类似身份证信息
第三步:解剖对象内存(法医现场)
# 在HSDB控制台输入(假设对象地址是0x7f0a7230)
hsdb> inspect 0x7f0a7230
你会看到这样的裸奔数据:
// 对象头(12字节)
0x7f0a7230: 0x0000000000000001 (mark word)
0x7f0a7238: 0x0000000800c01280 (klass pointer)
// 实例数据
0x7f0a7240: 0x0000002a // int id = 42
0x7f0a7244: 0x01 // boolean flag = true
0x7f0a7248: 0x00000007f0a7230 // String name引用地址
// 对齐填充(为了8字节对齐)
0x7f0a7250: 0x0000000000000000
幽默解读:
- 对象头就像对象的身份证(记录婚姻状态:是否被synchronized锁占有)
- 对齐填充是JVM的强迫症表现(必须凑够8的倍数字节)
技术深挖
对象内存布局公式
对象内存 = 对象头(12B) + 实例数据 + 对齐填充
对象头详解:
| 组成部分 | 大小 | 内容说明 |
|---|---|---|
| Mark Word | 8B | 哈希码、GC年龄、锁状态标志等 |
| Klass Pointer | 4B | 类型指针(开启压缩指针时) |
内存地址验证实验
// 在HSDB中执行:
hsdb> mem 0x7f0a7240 3
// 输出应显示:
0x7f0a7240: 0x0000002a // id=42
0x7f0a7244: 0x00000001 // flag=true(实际存储为1)
0x7f0a7248: 0x7f0a7230 // name引用地址
防翻车指南
-
指针压缩陷阱
如果看到0x0000000800c01280这样的klass指针,说明开启了压缩指针(UseCompressedOops) -
大小端模式
内存数据显示为小端模式(低位在前),比如int值42显示为0x2a000000 -
对象存活验证
在HSDB中使用scanoops命令扫描堆内存,确保对象未被GC回收
彩蛋中的彩蛋:
试试在HSDB中执行universe命令,你会看到整个堆内存的上帝视角!