在 HotSpot 研发过程中,开发团队曾经编写(或收集)过不少 JVM 插件和辅助工具,它们存放在 HotSpot 源码 hotspot/src/share/tools 目录下;
将编译好的插件放到 JDK_HOME/jre/bin/server
目录(JDK 9以下)或 JDK_HOME/lib/amd64/server
(JDK 9或以上)即可使用;
Ideal Graph Visualizer
: 用于可视化展示 C2 即时编译器将字节码转化为理想图,然后转化为机器码的过程;Client Compiler Visualizer
: 用于查看 C1 即时编译器生成高级中间表示(HIR),转换成低级中间表示(LIR)和做物理寄存器分配的过程;MakeDeps
: 帮助处理 HotSpot 的编译依赖;Project Creator
: 帮忙生成Visual Studio
的.project
;LogCompilation
: 将 -XX:+LogCompilation 输出的日志整理成易读的格式;HSDIS
: 即时编译器的反汇编插件;
文章目录
1. HSDIS
即时编译器的反汇编插件;把即时编译器动态生成的本地代码还原成汇编代码输出,附带大量注释;
代码示例
public class Bar {
int a = 1;
static int b = 2;
public int sum(int c) {
return a + b + c;
}
public static void main(String[] args) {
new Bar().sum(3);
}
}
执行编译
# -Xcomp 以编译模式执行代码,直接触发即时编译
# -XX:CompileCommand 让编译器不要内联 sum() 并只编译 sum()
# 输出反编译内容
# Product 版 VM 需要额外开启
java -XX:+PrintAssembly -XX:+UnlockDiagnosticVMOptions -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:CompileCommand=compileonly,*Bar.sum
反汇编结果
[Disassembling for mach='i386']
[Entry Point]
[Constants]
# {method} 'sum' '(I)I' in 'test/Bar'
# this: ecx = 'test/Bar'
# parm0: edx = int
# [sp+0x20] (sp of caller)
......
0x01cac407: cmp 0x4(%ecx),%eax
0x01cac40a: jne 0x01c6b050 ; {runtime_call}
[Verified Entry Point]
0x01cac410: mov %eax,-0x8000(%esp)
0x01cac417: push %ebp
0x01cac418: sub $0x18,%esp ; *aload_0
; - test.Bar::sum@0 (line 8)
;; block B0 [0, 10]
0x01cac41b: mov 0x8(%ecx),%eax ; *getfield a
; - test.Bar::sum@1 (line 8)
0x01cac41e: mov $0x3d2fad8,%esi ; {oop(a 'java/lang/Class' = 'test/Bar')}
0x01cac423: mov 0x68(%esi),%esi ; *getstatic b
; - test.Bar::sum@4 (line 8)
0x01cac426: add %edx,%eax
0x01cac428: add %esi,%eax
0x01cac42a: add $0x18,%esp
0x01cac42d: pop %ebp
0x01cac42e: test %eax,0x2b0100 ; {poll_return}
0x01cac434: ret
- mov %eax,-0x8000(%esp): 检查栈溢;
- push %ebp: 保存上一栈帧基址;
- sub $0x18,%esp: 给新帧分配空间;
- mov 0x8(%ecx),%eax: 取实例变量 a,这里 0x8(%ecx) 就是 ecx+0x8 的意思,前面代码片段
[Constants]
中提示了this:ecx='test/Bar'
,即 ecx 寄存器中放的就是 this 对象的地址;偏移 0x8 是越过 this 对象的对象头,之后就是实例变量 a 的内存位置;这次是访问 Java 堆中的数据; - mov $0x3d2fad8,%esi: 取 test.Bar 在方法区的指针;
- mov 0x68(%esi),%esi: 取类变量 b,这次是访问方法区中的数据;
- add %esi,%eax、add%edx,%eax: 做 2 次加法,求 a+b+c 的值,前面的代码把 a 放在 eax 中,把 b 放在 esi 中,而 c 在
[Constants]
中提示了,parm0:edx=int
,说明 c 在 edx 中; - add $0x18,%esp: 撤销栈帧;
- pop %ebp: 恢复上一栈帧;
- test %eax,0x2b0100: 轮询方法返回处的 SafePoint;
- ret: 方法返回;
2. JITWatch
经常与 HSDIS 搭配使用的可视化变异日志分析工具,可以方便的查看相应类和方法的 Java 源码、字节码、即时编译生成的汇编代码等;
VM Arguments 设置
-XX:+UnlockDiagnosticVMOptions
-XX:+TraceClassLoading
-XX:+LogCompilation
-XX:LogFile=/tmp/logfile.log
-XX:+PrintAssembly
上一篇:「JVM 故障诊断」可视化工具
下一篇:「JVM 故障诊断」故障分析与处理案例
PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!
参考资料:
- [1]《深入理解 Java 虚拟机》