什么是JITWatch?
什么是JITWatch?
大多数情况下,通过诸如javap
等反编译工具来查看源码的字节码已经能够满足我们的日常需求。
但是不排除在有些特定场景下,我们需要通过反汇编来查看相应的汇编指令。
JITWatch
——JIT
编译日志分析工具 提供了可视化界面帮助我们直观的查询汇编代码。
字节码指令和汇编指令
字节码指令和汇编指令区别
Java我们知道是一种跨平台语言,那么Java如何实现平台无关性呢?这就需要我们了解JVM
和Java的字节码文件。
这里我们需要有一点共识,就是任何一门编程语言都需要转换为与平台相关的汇编指令才能够最终被硬件执行,比如C和C++都将我们的源代码直接编译成与CPU相关的汇编指令给CPU执行。
不同系列的CPU的体系架构不同,所以它们的汇编指令也有不同,比如X86
架构的CPU
对应于X86
汇编指令,arm
架构的CPU对应于arm
汇编指令。
如果将程序源代码直接编译成与硬件相关的底层汇编指令,那么程序的跨平台性也就大打折扣,但执行性能相对较高。
为了实现平台无关性,Java的编译器javac
并不是将Java的源程序直接编译成与平台相关的汇编指令,而是编译成一种中间语言,即Java的class字节码文件。字节码文件,顾名思义存的就是字节码,即一个一个的字节。
Java源码编译后的字节码文件是不能够直接被CPU执行的,那么该如何执行呢?
答案是JVM
,为了让Java程序能够在不同的平台上执行,Java官方提供了针对于各个平台的Java虚拟机。
JVM
运行于硬件层之上,屏蔽各种平台的差异性。
javac
编译后的字节码文件统一由JVM
来加载,最后再转化成与硬件相关的机器指令被CPU执行。
知道了通过JVM
来加载字节码文件,那么还有一个问题,就是JVM
如何将字节码中的每个字节和我们写的Java源代码相关联,也就是JVM
如何知道我们写的Java源代码对应于Class文件中的哪段十六进制,这段十六进制是干什么的,执行了什么功能?
所以这就需要定义一个JVM
层面的规范,在JVM
层面抽象出一些我们能够认识的指令助记符,这些指令助记符就是Java的字节码指令。
字节码文件为什么存储十六进制?
字节码文件里面存的并不是二进制,而是十六进制,这是因为二进制太长了,一个字节要由8位二进制组成。所以用十六进制标表示,两个十六进制就可以表示一个字节。
安装执行步骤
下载并执行JITWatch
下载JITWatch
首先去到JITWatch的官方开源仓库:github.com/AdoptOpenJD…
选择需要下载的版本,这里建议使用1.4.2版本,1.4.2+版本的会出现乱码问题。
执行JITWatch
根据自己的操作系统选择并解压后,进入到项目主目录,使用maven
命令启动项目
mvn clean compile exec:java
复制代码
如果这里出现报错错误: 找不到或无法加载主类 org.adoptopenjdk.jitwatch.launch.LaunchUI
,说明你的JDK没有自带JavaFX
包。
这里我的建议是下载自带JavaFX
的JDK如Oracle JDK 11+
在解决完问题命令执行成功后会弹出JITWatch的GUI界面:
快速运行JITWatch
按照顺序试运行解析默认代码
我们按照上图的顺序依次点击Sandbox-> Configure Sandbox
,选择汇编语言。
这里正常来说这一步我们的Show Disassembly选项是不可选中的。这将会导致我们接下来执行Run的时候弹出框的右栏不显示东西。
这个问题我会带着大家在下面解决。
我们点击Run按钮:
弹出了代码的分析界面,分为左中右三栏,左边为Java源码,中间为Java字节码,右边为JIT后的汇编码,如果进行多次JIT编译,还能分别查看编译结果,如下图所示:
但是正如我们上面所说这里的Assembly栏大概率不会出现内容(参考这个提问:stackoverflow.com/questions/4…)
如果需要出现内容在这一步的选项需要能够被选中:
大致意思是:这是 JITWatch 与 FCML 反汇编程序一起使用时出现的错误。
在网上查阅大量资料告知需要hsdis
。如果没有这个东西,直接运行代码加上如下虚拟机参数后
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
复制代码
会得到如下提示:
Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled
复制代码
于是接下来我们就来安装HSDIS
获取hsdis并正确执行JITWatch
什么是HSDIS?
HSDIS
是HotSpot disassembler
的缩写,要使用HSDIS
需要下载hsdis-amd64.dylib
下载准备
- 下载Cygwin安装包(Win64)
- 下载GNU binutils
- 下载OpenJDK源码
安装Cygwin
镜像使用网易的,mirrors.163.com/,速度还算行
然后再Select Packages页面需要注意,在默认的基础上,需要额外安装的package有:
- Devel - make
- Devel - gcc-core
- Devel - mingw64-x86_64-gcc-core
- Devel - texinfo
- Utils - diffutils
安装完之后启动Cygwin,则会在Cygwin的安装目录下的home
下自动生成你的用户名命名的文件夹(下文记之为~
)
测试Cygwin
运行Cygwin,在弹出的命令行窗口输入:
cygcheck -c cygwin
复制代码
会打印出当前Cygwin的版本和运行状态,如果status是ok的话,则Cygwin运行正常:
Cygwin Package Information
Package Version Status
cygwin 3.3.6-1 OK
复制代码
接着可以测试一些基本环境是否成功安装:
g++ --version
make –version
...
复制代码
准备binutils和hsdis
将下载的binutils解压到~
下 .../home/用户名/binutils-2.27/
将下载的openJDK源码/src/share/tools/hsdis解压到~
下 .../home/用户名/hsdis/
修改~/hsdis/Makefile文件,搜索LIBRARIES,将
LIBRARIES = $(TARGET_DIR)/bfd/libbfd.a \ $(TARGET_DIR)/opcodes/libopcodes.a \ $(TARGET_DIR)/libiberty/libiberty.a
复制代码
修改为:
LIBRARIES = $(TARGET_DIR)/bfd/libbfd.a \ $(TARGET_DIR)/zlib/libz.a \ $(TARGET_DIR)/opcodes/libopcodes.a \
$(TARGET_DIR)/libiberty/libiberty.a
复制代码
保存,并且在Cygwin窗口输入命令
cd ~/hsdis
make OS=Linux MINGW=x86_64-w64-mingw32 'AR=$(MINGW)-ar' BINUTILS=~/binutils-2.27 #(最后的binutils路径需要与实际安装路径一致)
复制代码
等待几分钟,如果最后没有报错,那么就build成功了,我们需要的hsdis-amd64.dll
现在就在~/hsdis/build/Linux-amd64
里面了,把它copy到$JAVA_HOME\bin\server
下。
重新运行JITWatch
mvn clean compile exec:java
复制代码
这个时候弹出的JITWatch界面的Show Disassembly
选项就是可选中的:
并且可以成功的运行解析代码:
JITWatch解析本地Java代码
添加虚拟机参数,输出日志文件
我们先在idea中给想要解析的Class配置虚拟机参数:
-Xcomp
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintAssembly
-XX:+LogCompilation
-XX:LogFile=jit.log
复制代码
运行IDEA后,汇编代码不仅打印输出到终端,还会生成我们所需的日志文件:
用JITWatch进行可视化分析
首先点击 Config 按钮来设置Java源码文件、Class字节码文件所在路径:
然后点击 Open Log 来导入我们刚刚生成的日志文件,然后点击 Start 即可。现在我们可以从左侧选择我们需要分析的类,然后在右侧列出的方法上右击并选择 Show TriView 即可进行分析:
从下面可以看到,JITWatch可以同时显示源码、字节码、汇编,大大方便了我们的分析工作: