本文内容过于硬核,建议有 Java 相关经验人士阅读。
1. 可视化工具
在 JDK 中为我们提供了大量的 JVM 故障处理工具,都在 JDK 的 bin 目录下:
这其中除了大量的命令行工具以外,还为我们提供了更加方便快捷的可视化工具,主要是以下这 4 个:
JConsole: 最古老的工具,早在 JDK 5 时期就已经存在的虚拟机监控工具。
JHSDB: 名义上在 JDK 9 中才正式提供,但之前已经以 sa-jdi.jar 包里面的 HSDB(可视化工具) 和 CLHSDB(命令行工具) 的形式存在了很长一段时间。
VisualVM: 在 JDK 6 Update 7 中首次发布,直到 JRockit Mission Control 与 OracleJDK 的融合工作完成之前,它都曾是 Oracle 主力推动的多合一故障处理工具,现在它已经从 OracleJDK 中分离出来,成为一个独立发展的开源项目。
JMC: Java Mission Control ,曾经是大名鼎鼎的来自 BEA 公司的图形化诊断工具,随着 BEA 公司被 Oracle 收购,它便被融合进 OracleJDK 之中。在 JDK 7 Update 40 时开始随 JDK 一起发布,后来 Java SE Advanced 产品线建立, Oracle 明确区分了 Oracle OpenJDK 和 OracleJDK 的差别, JMC 从 JDK 11 开始又被移除出 JDK 。
2. HSDB
HSDB(Hotspot Debugger) 是 JDK 自带的工具,用于查看 JVM 运行时的状态。
使用方式由于在 JDK 9 之前没有正式提供,所以也未在 JDK 的 bin 目录下提供直接可执行文件,需要在命令行执行命令才能启动。
首先在命令行中先 cd 至 C:\Program Files\Java\jdk1.8.0_221\lib 目录,然后执行:
1
java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
我在执行这句命令的时候报了个错:
Exception in thread "Thread-1" java.lang.UnsatisfiedLinkError: Can't load library: C:\Program Files\Java\jdk-11.0.4\bin\sawindbg.dll
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2620)
at java.base/java.lang.Runtime.load0(Runtime.java:767)
at java.base/java.lang.System.load(System.java:1831)
at sun.jvm.hotspot.debugger.windbg.WindbgDebuggerLocal.<clinit>(WindbgDebuggerLocal.java:661)
at sun.jvm.hotspot.HotSpotAgent.setupDebuggerWin32(HotSpotAgent.java:567)
at sun.jvm.hotspot.HotSpotAgent.setupDebugger(HotSpotAgent.java:335)
at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:304)
at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
at sun.jvm.hotspot.HSDB.attach(HSDB.java:1184)
at sun.jvm.hotspot.HSDB.access$1700(HSDB.java:53)
at sun.jvm.hotspot.HSDB$25$1.run(HSDB.java:456)
at sun.jvm.hotspot.utilities.WorkerThread$MainLoop.run(WorkerThread.java:66)
at java.base/java.lang.Thread.run(Thread.java:834)
含义是有一个 sawindbg.dll 在 jdk 的目录下找不到,因为我本地有多个 jdk ,配置环境变量的是 jdk 11 ,我在 jdk 8 的 jre 的 bin 目录下找到了这个文件,直接 copy 到 jdk 11 的bin 目录下解决此问题。
执行完成后会打开这么个界面:
接下来,我们写一小段测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class A {
public void printMe() {
System.out.println("I am A class");
}
public abstract void sayHello();
}
public class B extends A {
@Override
public void sayHello() {
System.out.println("I am B class");
}
}
public class HSDB_Test {
public static void main(String[] args) throws IOException {
A obj = new B();
// 无意义,单纯用作卡住主线程,防止线程结束
System.in.read();
System.out.println(obj);
}
}
首先在命令行中使用命令 jps -l 查看 Java 进程的 pid :
1
2
3
4
5
6
7
8
9
PS C:\Users\inwsy> jps -l
1648 com.geekdigging.lesson04.jvmtools.HSDB_Test
8704
9280 org.jetbrains.jps.cmdline.Launcher
14724 jdk.jcmd/sun.tools.jps.Jps
3220 org/netbeans/Main
15144
20572 org.jetbrains.jps.cmdline.Launcher
7548 sun.jvm.hotspot.HSDB
可以看到,我们刚写的测试方法的 pid 是 1648 ,在 HSDB 中点击 File > Attach to Hotspot process :
第一个看到的就是当前进程中的线程:
到这里,我们的准备工作就已经结束,接着我们使用 Tools > Class Browser 找到对象 B 的内存地址:
图中红框框起来的是我自己写的三个类,可以看到我这里 B 的内存地址是 0x00000007c0060c18 。
接下来,使用 Tools > Inspector 查看这个对象的详细信息:
vtable 是虚表方法,这里我们看到 class B 有 7 个虚表方法,因为所有的对象都继承自 Object ,所以 B 继承了 Object 的 5 个方法,然后还继承了 A 的一个方法,自己重写了一个方法,总共是 7 个方法。
这个我们可以进行一下验证,可以在 Windows > Console 中使用 mem 命令进行查看。
那么我们可以开始计算, vtable 是在 instanceKlass 对象实例的尾部,而 instanceKlass 大小在 64 位系统的大小为 0x1B8 ,因此 vtable 的起始地址等于 instanceKlass 的内存首地址加上 0x1B8 等于 7C0060DD0 。
接下来,我们在 Windows > Console 中使用 mem 命令进行验证:
第一列是方法实际在堆中的内存地址,第二列则是内存指针地址,我们可以将拿到的内存指针地址去 A , B 和 Object 中分别查看,可以看到前 5 行对应的是 Object 的方法,第 6 行对应的是 A 对象中的方法,第 7 行则对应 B 对象中的方法。