hsdis 和 jitwatch
# hsdis 和 jitwatch
# 介绍
学习并发时,关于 volatile 关键字的实现原理: **编译器将在volatile字段的读写操作前后各插入一些内存屏障。**其使用到了汇编码中的 lock 指令前缀。于是就产生了一个想法: Java 代码生成的汇编码是什么样子的?如何将 Java 代码与汇编码相对应?
比如这个类:
public class TestVolatile {
public volatile long sum = 0;
public int add(int a, int b) {
int temp = a + b;
sum += temp;
return temp;
}
public static void main(String[] args) {
TestVolatile test = new TestVolatile();
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum = test.add(sum, 1);
}
System.out.println("Sum:" + sum);
System.out.println("Test.sum:" + test.sum);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
而通过 hsdis 和 jitwatch 工具可以得到编译后的汇编代码。
安装环境:
- Mac OS 12.6
- jdk1.8.0_131
# 安装 hsdis
hsdis(HotSpot disassembly) 是 Sun 官方推荐的 HotSpot VM JIT 编译代码的反汇编插件。由于使用 jdk8,需要引入 hsdis-amd64.dylib
- 下载 hsdis:百度网盘 (opens new window),密码:uigv
- 将 hsdis-adm64.dylib 文件放到 jre/lib 目录下即可,本机目录地址:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib
# 配置 IntelliJ IDEA 运行参数,获取汇编日志
下面会用到的 java
命令参数的解释:
-XX:+UnlockDiagnosticVMOptions
:解锁用于 JVM 诊断的选项。
-XX:+PrintAssembly
:配合反汇编插件(例如 hsdis-amd64.dylib
)可以打印出字节码和本地方法的汇编码;必须和 -XX:+UnlockDiagnosticVMOptions
一起使用。
-Xcomp
:在第一次调用时强制编译方法。默认情况下,无论是 -client
模式还是 -server
模式,都需要执行一定次数解释方法的调用才会触发方法的编译。(如果需要 JIT 日志,则不指定该参数)
-XX:CompileCommand=compileonly,*ClassName.methodName
:只编译类名为 ClassName
中的 methodName
方法,支持使用 *
作为通配符。可以多次指定 -XX:CompileCommand
添加多条命令。(建议只指定需要的方法,否则将会产生大量的无关日志)
-XX:+LogCompilation
:允许将编译活动记录到当前工作目录中名为 hotspot.log
的文件中。可以通过 -XX:LogFile
指定文件的路径和名字。
IDEA 运行参数可简单配置为:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
IDEA 执行结果:
也可以指定类中某个方法,结果保存到文件中,配置如下:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=compileonly,*VisibilityTest.add -XX:+LogCompilation -XX:LogFile=~/VisibilityTest.log
# 安装 jitwatch
使用 IDEA 或 文本编辑器查看汇编 log 文件不太方便,可以是 jitwatch 工具。jitwatch 是 GitHub 上的一个开源项目,一个用于分析汇编日志的图形界面工具。
下载 jitwatch:AdoptOpenJDK/jitwatch (opens new window)
启动:
mvn clean compile test exec:java
配置:
# 使用 jitwatch sandbox 分析 JIT 汇编代码
Sandbox 的作用呢就是直接运行代码里面的 main 函数,然后根据代码的执行情况会生成 JIT 日志,执行完成之后可以分析这个过程中的 JIT 日志。需要注意的是:这里必须达到了 JIT 的条件才会生成 JIT compile log,例如达到一定的调用次数。
以上面的 TestVolatile 为例,没有使用 volatile 关键字修饰 sum 变量:
使用 volatile 关键字修饰 sum 变量:
可以发现使用 volatile 关键字后多了一个 lock 指令:
0x0000000111cb8f5e: lock addl $0x0,(%rsp) ;*putfield sum
; - TestVolatile::add@12 (line 6)
2
# 参考
- https://github.com/funnycoding/blog/issues/40
- https://hengyun.tech/jvm-hsdis-jitwatch/