本机环境:Windows10 jdk1.8
本文参考书本《深入理解Java虚拟机》第四章
1 这边直接把书中的代码拿过来了(请看下注释)
// 注意这里的包名路径,后面会用到
package vip.mate.module.accounting.controller;
public class JHSDB_TestCase {
static class Test{
static ObjectHolder staticObj = new ObjectHolder();
ObjectHolder instanceObj = new ObjectHolder();
void foo(){
ObjectHolder localObj = new ObjectHolder();
System.out.println("done"); // 这里打断点
}
}
private static class ObjectHolder {}
public static void main(String[] args) {
Test test = new JHSDB_TestCase.Test();
test.foo();
}
}
staticObj 存在Java静态变量的地方,概念上在JVM的方法区里
instanceObj 是新创建的对象,肯定在堆中分配内存
localObj 是foo()方法的局部变量(只有执行到此方法,该变量才会存在,所以需要在代码中的这边打断点)
2 debugge启动JHSDB_TestCase类(打断点,保证三个变量已经在内存中分配好)
为了加快在内存中的搜索对象的速度,我们限制以下java堆大小
-Xmx20m -XX:+UseSerialGC -XX:-UseCompressedOops
3 打开cmd,输入 jps-l,如下图,找到JHSDB_TestCase类的pid
4 下面启动HSDB,紧接着在上面cmd中输入以下命令,此时会弹出HSDB的图形化界面如下
java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB
5 从菜单里选择File -> Attach to HotSpot process
5 把刚才cmd中的查到的pid输入进来,点击ok
6 现在就连接到我们的目标进程了
7 现在就连接到我们的目标进程了,直接在菜单里选择Windows -> Console,会得到一个空白的Command Line窗口,在里面敲一下回车就会出现hsdb>提示符。
8 通过universe命令来查看GC堆的地址范围和使用情况
hsdb> universe
Heap Parameters:
Gen 0: eden [0x0000000012800000,0x0000000012b93598,0x0000000012d60000) space capacity = 5636096, 66.52250068132267 used
from [0x0000000012e00000,0x0000000012e9fff8,0x0000000012ea0000) space capacity = 655360, 99.998779296875 used
to [0x0000000012d60000,0x0000000012d60000,0x0000000012e00000) space capacity = 655360, 0.0 usedInvocations: 1
Gen 1: old [0x0000000012ea0000,0x0000000012ff4608,0x0000000013c00000) space capacity = 14024704, 9.940915687061915 usedInvocations: 0
hsdb>
GC堆由young gen(年轻代)和old gen(老年代)构成
年轻代又由一个eden(伊甸区)和两个survivor space(幸存区)构成
9 通过scanoops命令查看三个对象的具体位置
注:这里提到“内存地址”是指虚拟内存意义上的地址,不是“物理内存地址”
// 这里包名一定要写全
scanoops 0x0000000012800000 0x0000000012d60000 vip.mate.module.accounting.controller.JHSDB_TestCase$ObjectHolder
注意上图圈红的地方,我是直接在eden区间范围搜索的,三个对象在这分配了地址
验证
1 根据对象实例地址找出引用他们的指针(revptrs 命令)
revptrs 0x0000000012b7c710
这里找到一个引用该对象的地方,是在一个java.lang.Class的实例里,并且给出了实例地址,下图圈红的地方
2 通过Tools -> Inspector功能再确认以下这三个地址中存放的对象
3 前两个对象可以重复上面两步,当我们通过以上命令验证第三个对象的时候,JHSDB返回了一个null,所以revptrs 命令不支持查找栈上的引用
4 我们人工来查找这第三个对象,在Java Thread窗口选中main线程后点击Stack Memory按钮查看该线程栈内存,如下图
以下文字来自书本:
这个线程只有两个方法栈帧,尽管没有查找功能,但通过肉眼观察地址
ox0000000002fef678上的值正好就是0x0000000012b7c748,而且JHSDB在旁边已经自动生成注释,说明这里确实是引用了一个来自新生代的JHSDB_TestCase$ObjectHolder。至此,本实验中的三个对象均已找到,并成功追溯到引用他们的地方。