深入理解java虚拟机jhsdb:基于服务性代理的调试工具(代码片段)

、Dong 、Dong     2022-12-28     453

关键词:


前言

JHSDB虽然是JDK 9中才正式提供,但之前已经以sa-jdi.jar包里面的HSDB(可视化工
具)和CLHSDB(命令行工具)的形式存在了很长一段时间。它们两个都是JDK的正式成员,随着JDK一同发布,无须独立下载,使用也是完全免费的。

JDK中提供了JCMD和JHSDB两个集成式的多功能工具箱,它们由于有着“后发优势”,能够做得往往比之前的老工具们更好、更强大,下表所示是JCMD、 JHSDB与原基础工具实现相同功能的简要对比。


本篇的主题是可视化的故障处理,所以JCMD及JHSDB的命令行模式就不再作重点讲解了,接下来我们通过一个实验来讲解JHSDB的图形模式下的功能。


一、JHSDB介绍

JHSDB是一款基于服务性代理( Serviceability Agent, SA)实现的进程外调试工具。服务性代理是HotSpot虚拟机中一组用于映射Java虚拟机运行信息的、主要基于Java语言(含少量JNI代码)实现的API集合。服务性代理以HotSpot内部的数据结构为参照物进行设计,把这些C++的数据抽象出Java模型对象,相当于HotSpot的C++代码的一个镜像。通过服务性代理的API,可以在一个独立的Java虚拟机的进程里分析其他HotSpot虚拟机的内部数据,或者从HotSpot虚拟机进程内存中dump出来的转储快照里还原出它的运行状态细节。服务性代理的工作原理跟Linux上的GDB或者Windows上的Windbg是相似的。现在,我们要借助JHSDB来分析一下代码清单中的代码,并通过实验来回答一个简单问题: staticObj、 instanceObj、 localObj这三个变量本身(而不是它们所指向的对象)存放在哪里?

/**
* staticObj、 instanceObj、 localObj存放在哪里?
*/
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随着Test的类型信息存放在方法区, instanceObj随着Test的对象实例存放在Java堆, localObject则是存放在foo()方法栈帧的局部变量表中,现在要做的是通过JHSDB来实践验证这一点。


二、JHSDB实操

首先,我们要确保这三个变量已经在内存中分配好,然后将程序暂停下来,以便有空隙进行实
验,这只要把断点设置在代码中加粗的打印语句上,然后在调试模式下运行程序即可。由于JHSDB本身对压缩指针的支持存在很多缺陷,建议用64位系统的读者在实验时禁用压缩指针,另外为了后续操作时可以加快在内存中搜索对象的速度,也建议读者限制一下Java堆的大小。本例中,笔者采用的运行参数如下:

-Xmx10m -XX:+UseSerialGC -XX:-UseCompressedOops

程序执行后通过jps查询到测试程序的进程ID,具体如下:

jps -l
8440 org.jetbrains.jps.cmdline.Launcher
11180 JHSDB_TestCase
15692 jdk.jcmd/sun.tools.jps.Jps

使用以下命令进入JHSDB的图形化模式,并使其附加进程11180:

jhsdb hsdb --pid 11180

命令打开的JHSDB的界面如图所示。

阅读上述JHSDB_TestCase类代码清单可知,运行至断点位置一共会创建三个ObjectHolder对象的实例,只要是对象实例必然会在Java堆中分配,既然我们要查找引用这三个对象的指针存放在哪里,不妨从这三个对象开始着手,先把它们从Java堆中找出来。

首先点击菜单中的Tools->Heap Parameters,结果如图所示,因为笔者的运行参数中指定了使用的是Serial收集器,图中我们看到了典型的Serial的分代内存布局, Heap Parameters窗口中清楚列出了新生代的Eden、 S1、 S2和老年代的容量(单位为字节)以及它们的虚拟内存地址起止范围。

如果不指定收集器,即使用JDK默认的G1的话,得到的信息应该类似如下所示:

Heap Parameters:
garbage-first heap [0x00007f32c7800000, 0x00007f32c8200000] region size 1024K

请读者注意一下图中各个区域的内存地址范围,后面还要用到它们。打开Windows->Console窗口,使用scanoops命令在Java堆的新生代(从Eden起始地址到To Survivor结束地址)范围内查找ObjectHolder的实例,结果如下所示:

hsdb>scanoops 0x00007f32c7800000 0x00007f32c7b50000 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c458 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c480 JHSDB_TestCase$ObjectHolder
0x00007f32c7a7c490 JHSDB_TestCase$ObjectHolder

果然找出了三个实例的地址,而且它们的地址都落到了Eden的范围之内,算是顺带验证了一般情况下新对象在Eden中创建的分配规则。再使用Tools->Inspector功能确认一下这三个地址中存放的对象,结果如图所示。


Inspector为我们展示了对象头和指向对象元数据的指针,里面包括了Java类型的名字、继承关
系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口方法表(itable)等。由于我们的确没有在ObjectHolder上定义过任何字段,所以图中并没有看到任何实例字段数据,读者在做实验时不妨定义一些不同数据类型的字段,观察它们在HotSpot虚拟机里面是如何存储的。

接下来要根据堆中对象实例地址找出引用它们的指针,原本JHSDB的Tools菜单中有Compute
Reverse Ptrs来完成这个功能,但在笔者的运行环境中一点击它就出现Swing的界面异常,看后台日志是报了个空指针,这个问题只是界面层的异常,跟虚拟机关系不大,所以笔者没有继续去深究,改为使用命令来做也很简单,先拿第一个对象来试试看:

hsdb> revptrs 0x00007f32c7a7c458
Computing reverse pointers...
Done.
Oop for java/lang/Class @ 0x00007f32c7a7b180

果然找到了一个引用该对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面有一个名为staticObj的实例字段,如图所示。


从《Java虚拟机规范》所定义的概念模型来看,所有Class相关的信息都应该存放在方法区之中,但方法区该如何实现,《Java虚拟机规范》并未做出规定,这就成了一件允许不同虚拟机自己灵活把握的事情。 JDK 7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射Class对象存放在一起,存储于Java堆之中,从我们的实验中也明确验证了这一点。接下来继续查找第二个对象实例:

hsdb>revptrs 0x00007f32c7a7c480
Computing reverse pointers...
Done.
Oop for JHSDB_TestCase$Test @ 0x00007f32c7a7c468

这次找到一个类型为JHSDB_TestCase$Test的对象实例,在Inspector中该对象实例显示如图所示。

这个结果完全符合我们的预期,第二个ObjectHolder的指针是在Java堆中JHSDB_TestCase$Test对象的instanceObj字段上。但是我们采用相同方法查找第三个ObjectHolder实例时, JHSDB返回了一个null,表示未查找到任何结果:

hsdb> revptrs 0x00007f32c7a7c490
null

看来revptrs命令并不支持查找栈上的指针引用,不过没有关系,得益于我们测试代码足够简洁,人工也可以来完成这件事情。在Java Thread窗口选中main线程后点击Stack Memory按钮查看该线程的栈内存,如图所示。


这个线程只有两个方法栈帧,尽管没有查找功能,但通过肉眼观察在地址0x00007f32e771c998上的值正好就是0x00007f32c7a7c490,而且JHSDB在旁边已经自动生成注释,说明这里确实是引用了一个来自新生代的JHSDB_TestCase$ObjectHolder对象。至此,本次实验中三个对象均已找到,并成功追溯到引用它们的地方,也就实践验证了开篇中提出的这些对象的引用是存储在什么地方的问题。

JHSDB提供了非常强大且灵活的命令和功能,本篇的例子只是其中一个很小的应用,我们在实际开发、学习时,可以用它来调试虚拟机进程或者dump出来的内存转储快照,以积累更多的实际经验。


结尾

  • 感谢大家的耐心阅读,如有建议请私信或评论留言。
  • 如有收获,劳烦支持,关注、点赞、评论、收藏均可,博主会经常更新,与大家共同进步

深入理解java虚拟机-常用vm参数分析

Java虚拟机深入理解系列全部文章更新中...深入理解Java虚拟机-Java内存区域透彻分析深入理解Java虚拟机-常用vm参数分析深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别JVM内存分配文盲深入理解Java虚拟机-如何利用JDK自带... 查看详情

深入理解java虚拟机

一、运行时数据区域? 1、程序计数器:当前线程执行字节码的行号指示器(通过改变计数器的值来选择下条需要执行的字节码指令)每个线程有独立的程序计数器(线程私有,为了切换线程时能恢复到挣钱的执行位置)如果... 查看详情

深入理解java虚拟机-如何利用visualvm对高并发项目进行性能分析

Java虚拟机深入理解系列全部文章更新中...深入理解Java虚拟机-Java内存区域透彻分析深入理解Java虚拟机-常用vm参数分析深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别JVM内存分配文盲深入理解Java虚拟机-如何利用JDK自带... 查看详情

深入理解java虚拟机类加载机制

本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。本系列其他文章:【深入理解Java虚拟机】Java内存区域模型、对象创建过程、常见OOM【深入理解Java虚拟机】垃圾回收机制1、类加载机制概述虚拟机把... 查看详情

深入理解java虚拟机

title:深入理解Java虚拟机date:2020-05-1410:58:24tags:JVM,虚拟机目录title:深入理解Java虚拟机date:2020-05-1410:58:24tags:JVM,虚拟机1.运行时数据区域2.GC垃圾回收3.内存分配与回收策略4.类加载机制1.加载2.验证3.准备4.解析5.初始化5.类与类加载器1.... 查看详情

深入理解java虚拟机走进java

1.JDK:java程序设计语言、java虚拟机、javaAPI二、自动内存管理机制-----------------------------------------------------  1.运行时数据区域:    (1)java虚拟机在执行java程序的过程中会把所管理的内存划分为若干个不同的数据区域。这些... 查看详情

深入理解java虚拟机

 让我们开启java虚拟机的愉快之旅。一、java虚拟机的特点1、支持跨平台。2、支持多种语言,不止只有java语言,只有语言支持的相应的规范,即可java虚拟机中运行。二、java的发展版本说明java在不断更新和优化,其中有很多... 查看详情

深入理解jvm——虚拟机gc

对象是否存活Java的GC基于可达性分析算法(Python用引用计数法),通过可达性分析来判定对象是否存活。这个算法的基本思想是通过一系列"GCRoots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链... 查看详情

深入理解java虚拟机

1:java的体系结构  java程序设计语言  javaclass文件格式  java应用编程接口  java虚拟机2:java虚拟机的主要任务是装载class文件并且执行其中的代码。3:java有两种方法,Java方法和本地方法。4:通过本地方法,java程序可... 查看详情

深入理解jvm——虚拟机gc

对象是否存活Java的GC基于可达性分析算法(Python用引用计数法),通过可达性分析来判定对象是否存活。这个算法的基本思想是通过一系列"GCRoots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一... 查看详情

深入理解java虚拟机--垃圾收集及故障诊断

1.垃圾收集算法  1.1标记-清除算法     算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程上一篇博客说过,后续的几种算法都是基于这个算法对其不足进行改进.... 查看详情

深入理解java虚拟机--垃圾收集及故障诊断

1.垃圾收集算法  1.1标记-清除算法     算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,标记过程上一篇博客说过,后续的几种算法都是基于这个算法对其不足进行改进.... 查看详情

又到一年金三银四!深入理解java虚拟机第三版百度云

业界常用的服务注册与发现组件对比了解服务注册与发现的基本原理后,如果你要在项目中使用服务注册与发现组件,当面对众多的开源组件该如何进行技术选型?在互联网公司里,有研发实力的大公司一般会选... 查看详情

深入理解java虚拟机

java内存区域Java虚拟机执行java程序时会将管理的内存划分为若干个区域:   1. 程序计数器    程序计数器是一个”线程私有“的内存区域,用于获取下一条需要执行的字节码指令,如分支、循环、跳转等。  2.Ja... 查看详情

深入理解java虚拟机(类文件结构)

深入理解Java虚拟机(类文件结构)欢迎关注微信公众号:BaronTalk,获取更多精彩好文!之前在阅读ASM文档时,对于已编译类的结构、方法描述符、访问标志、ACC_PUBLIC、ACC_PRIVATE、各种字节码指令等等许多概念听起来都是云山雾... 查看详情

深入理解java虚拟机到底是什么?

好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java代码,我们写的代码都是在一个叫做Java虚拟机的东西上执行的。但是如果要问什么是虚拟机,恐怕很多人就会模... 查看详情

《深入理解java虚拟机》读后笔记-hotspot虚拟机对象探秘(代码片段)

文章目录《深入理解Java虚拟机》读后笔记-HotSpot虚拟机对象探秘1.对象的创建2.对象的内存布局2.1对象头2.2实例数据2.3对齐填充3.对象的访问定位《深入理解Java虚拟机》读后笔记-HotSpot虚拟机对象探秘基于实用优先的原则,这... 查看详情

深入理解java虚拟机

 java历史1996.01.23发布Jdk1.01998.12.04发布jdk1.2(里程碑的版本)注意:集合容器Collection和Map都是从1.2开始1999.04.27HotSpot虚拟机发布,成为1.3后SunJDK的默认虚拟机,这本书主要讲这个虚拟机2004.09.30发布jdk1.5这个版本也很重要,出... 查看详情