背景

Java代码通过编译器编译成字节码(class文件),由jVM的各个类加载器加载后,根据JVM的启动配置可以进行解释执行和编译执行。

编译执行是由JIT(Just In Time) Compiler将字节码一次性编译成本地代码来提高执行速度。缺点是编译本身会消耗并且会占用堆外空间(codecache中),但是一般Server应用内存足够且能够忍受启动时的略微缓慢。

查看源代码对应的字节码:javap

查看汇编代码请继续向下看。


分析程序如何执行时,大量自行代码时通过JIT编译器动态生成到CodeBuffer中的,通过软件条件工具(GDB、Windbg等)断点调试的方式在Java虚拟机中会遇到很大困难,HSDIS插件是Sun官方推荐的HotSpot虚拟机JIT编译代码的反汇编插件,下面介绍如何在Mac平台下使用。

安装HSDIS

查看是否安装HSDIS:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -version | grep Java HotSpot(TM)

如果有,则输出

没有,则下载

前者是Mac 64位机器,后者是Linux 64位机器。

下载后将hsdis-amd64.so放到jdk1.version.jdk/Contents/Home/jre/lib/amd64

安装JITWatch

分析展现JIT日志等的图形界面工具

1
2
3
git clone https://github.com/AdoptOpenJDK/jitwatch
cd jitwatch
./gradlew run
1
mvn clean install -DskipTests=true
1
./launchUI.sh

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicInteger_jdk8 {
private final static int TEST_SIZE = 100000000;
private final static int THREAD_COUNT = 10;
private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1);
private AtomicInteger ai = new AtomicInteger(0);
private long startTime;

public void init() {
startTime = System.nanoTime();
}

public class MyTask implements Runnable {
@Override
public void run() {
while (true) {
if (ai.getAndIncrement() == TEST_SIZE) {
System.out.println(System.nanoTime() - startTime);
cdl.countDown();
System.exit(0);
}
}
}
}

public static void main(String[] args) {
AtomicInteger_jdk8 at = new AtomicInteger_jdk8();
at.init();
for (int n = 0; n < THREAD_COUNT; n++)
new Thread(at.new MyTask()).start();
System.out.println("start");
at.cdl.countDown();
}
}

添加JVM参数

用于生成hotspot.log文件

.java文件

1
-XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading  -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=jit.log

添加参数后直接运行,会产生jit.log文件

Maven项目

有的项目会用maven,就用下面的方式添加参数,此处不用操作,仅仅作为说明

1
export "MAVEN_OPTS=$MAVEN_OPTS -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly"
  • 父目录执行
1
mvn tomcat:run

我们要开始玩啦~

  • 打开JITWatch界面,点击Open Log,选择jit.log,点击Start
  • 点击左侧 Packages 下的类,双击右侧具体的方法,可以打开TriView界面,在最右侧可以看到Assembly代码

重点来了

我的没有!!!

Assembly not found. Was -XX:+PrintAssembly option used?

经查阅,是因为JITWatch以前和FCML反汇编程序一起运行的bug,目前已经解决,但是看完了我也没懂到底我要干嘛,继续查文档…看到了刘正阳—hsdis,按照文档说明操作了一把,泪奔……终于看到了

正确姿势

  • 编译hsdis

make ARCH=AMD64

  • 将dylib复制到JDK目录中

如果JDK版本不同需要修改路径中版本号

1
sudo cp build/macosx-amd64/hsdis-amd64.dylib /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/server/
  • 再打开JITWatcher就可以看到了

参考资料