操作系统:macOS 10.14.6

JDK_Version:1.8.072

JITWatch_Version:jitwatch-1.3.0

1. 动机

学习并发时看到这么一句话,关于 volatile 关键字的:「编译器将在volatile字段的读写操作前后各插入一些内存屏障。」

于是我想去看看编译器是插入的内存屏障。

结果发现字节码中并没有在被 volatile 修饰的变量周围插入特殊的字节码。

后来经查询需要看的被 JIT 即时编译器编译过的汇编代码,这里就涉及到了对「汇编的反编译」。

2.准备工作

  1. 下载反汇编器:HotSpot Disassembler 其本质是一个动态链接库, MacOS使用的下载地址在这里 : https://github.com/liuzhengyang/hsdis/blob/master/build/macosx-amd64/hsdis-amd64.dylib
  2. 将下载好的 hsdis-amd64.dylib 放到 jdk/jre/lib 下 例如我的目录是:

/Library/Java/JavaVirtualMachines/jdk1.8.0_172.jdk/Contents/Home/jre/lib

  1. 使用 java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -version 检测反汇编器是否 OK 如果输出中你能看到下图中的 PringtAssembly is enabled 就代表 OK 了

  1. 安装 JITWatch ,GithubRepoUrl:https://github.com/AdoptOpenJDK/jitwatch
  2. 编译/启动 JITWatch gradlew clean build run 因为 这个版本的 JITWATCH 是用 Gradle 来管理依赖,而我本人还是比较喜欢 Maven,因为每次使用Gradle 只要版本不同,就得下载一大堆 Gradle 的依赖,这次也不例外 如下图:

image-20200409223827600

下载好之后这个shadowJar就自动打开了,软件就长这样:

OK,现在反汇编编译器也有了,查看 JIT 编译后的代码也有了,下一步就是生成一个 JIT Log 文件,同时注意,JIT 优化热点代码的前提之一就是有一定的循环数量,我的代码是这样的: 是极客时间课程中的一个样例代码,我加了100000次循环

public class VolatileExample {
    volatile int x = 0;
    volatile boolean v = false;

    public void writer() {
        x += 1;
        v = true;
    }

    public void reader() {
        if (v == true) {
            System.out.println(x);
        }
    }



    public static void main(String[] args) {
        VolatileExample example = new VolatileExample();
        for (int i = 0; i < 100000; i++) {
            example.writer();
        }
    }
}

  1. 编译 VolatileExample.java 命令: javac VolatileExample.java
  2. 配置执行时 JVM 参数,打印汇编日志文件
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading  -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=VolatileExample.log[即将生成的文件名]  VolatileExample[你的类名]

执行完毕后就得到了这么一个JIT Log文件

  1. 使用 「JIT Watch」 打开生成好的 JIT 日志文件
    1. 点击 OpenLog 打开刚才生成的JIT 文件
    2. 点击 Config 配置 源码 和 Class 文件的地址,由于我的 源码 和 字节码文件都在一起,所以我配置的都是一个地址
    3. 点击 Start 正常的话就可以看到左边 Packages 里面多出来了 你自己定义的类
    4. 点击你自己定义的类,跳入 TriView 视图

  1. 阅读 JIT Watcher 反编译出来的汇编代码

可以看到这里就是被 volatile 修饰的变量 底层汇编代码对这个关键字做的处理

从 Java 源码到字节码再到汇编代码,一路跟下来,确认了理论正确之后,长舒了一口气,眼见为实。 如果你也有类似的需求,希望这篇文章能帮助到你。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

最是人间留不住,曾是惊鸿照影来。