如何查看 HotSpot VM 的运行时数据

本文将借助 HSDB 工具分析 HotSpot VM 的运行时数据,运行的 Java 环境为 JDK 1.8。

运行上述代码,会在Java堆中生成3个Test对象,变量t1,t2,t3分别存储在方法区、实例字段和局部变量表中,那么Test对象的内存是如何布局的呢?

在查看运行时数据之前,需要让程序刚好执行完new Main().fn();并暂停,平时可能习惯了在Eclipse、IntelliJ IDEA、NetBeans等Java IDE里使用Java层调试器,但为了减少对外部工具的依赖,本文将使用Oracle JDK自带的jdb工具来完成此任务。

jdb使用步骤如下:
1、jdb -XX:+UseSerialGC -Xmx10m命令启动jdb;
2、stop in Main.fn命令指定在方法入口设置断点;
3、run Main命令指令主类,启动java程序;
4、next命令可以向前执行一步;

采用jps命令查看目前调试java程序的PID

采用命令java -cp sa-jdi.jar sun.jvm.hotspot.HSDB启动HSDB工具,并连接到目标进程上,注意:Windows上Oracle JDK7才可以用HSDB。

连接上之后

默认窗口是Java Threads,显示当前进程的线程列表,双击线程打开一个Oop Inspector窗口,显示该线程在HotSpot VM的对象。

在菜单里选择Windows -> Console,打开HSDB里的控制台,用命令查看更多信息。
1、命令universe查看GC堆的大小、地址范围和使用情况;

可以发现HotSpot在1.8的Java堆中,已经去除了Perm gen区,由youyoung gen和old gen组成。

2、命令scanoops查看指定类型的实例对象,接受两个必选参数和一个可选参数:必选参数是要扫描的地址范围,一个是起始地址一个是结束地址;可选参数用于指定要扫描什么类型的实例对象;

通过执行结果可以看出,Java堆上的确有3个Test实例对象,对象的开始地址分别为0x00000000ff6caf08、0x00000000ff6caf40和0x00000000ff6caf58。

3、命令whatis可以查看指定内存地址所在的区域;

上述结果可以发现3个Test实例对象都在分配给main线程的thread-local allocation buffer (TLAB)中。

4、命令inspect可以查看对象的内容;

instance of Oop for Test:表明该地址代表的对象是Test类的实例
_mark:对象头的第一个字段,记录该对象的状态
_metadata._compressed_klass:指向描述Test类信息的对象
name:实例的字段
id:实例的对象

可以发现_metadata._compressed_klass并没有显示内存地址,是因为该对象在java8中并非在堆中进行分配。

5、命令mem可以看更直接的数据,接受的两个参数,起始地址和以字宽为单位的“长度”;

可以发现_metadata._compressed_klass所指向的地址0x0000000111c10228已经超出了Java堆的最大地址,所以通过执行inspect 0x0000000111c10228并不会返回对象内容。

不过_metadata._compressed_klass的内容,可以通过在Inspector窗口中输入Test实例对象的开始地址进行查看。

  • InstanceKlass是类的描述对象,存储着Java类型名称、继承关系、接口、字段信息、方法信息、虚方法表和接口方法表等数据,不过InstanceKlass是给VM内部使用的,并不直接暴露给用户;
  • InstanceKlass中维护了一个字段_java_mirror,指向类的Class对象,所以当使用obj.getClass()获取Class对象时,是通过obj -> _klass -> _java_mirror的过程进行获取的;
  • 在jdk7之前,HotSpot把类的静态字段保存在InstanceKlass中;从jdk7开始,为了配合perm gem的移除工作,静态字段被移动到Class对象中,如Test类中的version变量,存放在_java_mirror所指向的Class对象中。

6、命令revptrs可以找出反向指针(如果变量a指向对象b,那么可以从b对象出发找到变量a);

查看第一个Test实例

这个变量在Class对象中,其实是Main类的Class对象,所以该变量为t1;
通过whatis命令查看该Class对象的分配位置

这个Class对象也是在eden里,具体来说在main线程的TLAB中,这个Class对象如何引用到Test类的实例?
通过inspect命令查看Class对象的内容

可以发现Main类的Class对象中存储了字段t1指向Test类的实例,该实例的起始地址正好是0x00000000ff6caf08。
JVM规范中并没明确规定静态变量的存放位置,通常应该放在“方法区”中,不过在jdk7的HtoSpot实现中,静态变量被保存在了Java堆中;
前面也提到过,在JDK7之前的HotSpot实现中,静态变量被保存在InstanceKlass里,并放在PermGen中;


查看下一个Test实例

这个变量在Main类一个实例中,为t2;
通过inspect命令查看Main实例的内容

在该实例中,的确存在字段t2指向起始地址为0x00000000ff6caf40的Test实例。


查看最后一个Test实例

结果null,说明没有找到…
排除了前面两个Test实例,说明这个实例对应的变量应该为t3,该变量t3被保存在Main.fn方法调用栈中。

选择main线程,并点击图示的按钮,打开Stack Memory窗口如下:

Stack Memory窗口中,包含Main.fn()Main.main()方法调用对应的栈帧,其中红色框框中对应Main.fn()的栈帧
第1列为内存地址,该地址指虚拟内存意义上的地址,而非物理地址;
第2列为该地址上的数据,以字宽为单位;
第3列是对数据的注释;

先看看栈帧的结构,一个栈帧从上到下,分别包含了操作数栈、栈帧信息和局部变量表。

通过inspect查看局部变量表数据,局部变量表第0位置slot[0] = “this”,指向 Main实例

局部变量表第1位置slot[1] = “t3″,指向Test实例,该实例正好是最后一个Test实例

参考
借HSDB来探索HotSpot VM的运行时数据

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

1 1 收藏 评论

关于作者:占小狼

我是占小狼。在魔都艰苦奋斗,白天是上班族,晚上是知识服务工作者。如果读完觉得有收获的话,记得关注和点赞哦。非要打赏的话,我也是不会拒绝的。 个人主页 · 我的文章 · 9 ·  

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部