如何在JVM中查看JIT编译的代码?

97

有没有一种方法可以查看JVM中JIT生成的本机代码?


你确定要查看JIT编译(本地)代码,还是只想看字节码?我问这个问题是因为在这里问这个问题会让人怀疑你是否真的想看本地代码...对不起,我也不知道这样的工具。 - gimpf
3
我想要查看确切的JIT编译本地代码。当然这不是我完成工作所需的东西,而更像是一种实验和调查的事情。 - alsor.net
小框架挑战:现代JVM中使用的动态编译器并不只有一个版本的编译代码;它可能开始解释,然后编译一个方法或其中的一部分,然后根据类的加载/卸载或使用模式的变化或基于性能统计信息多次重新编译它。 (我认为它甚至可以丢弃已编译的版本并返回解释,如果这似乎有益。)因此,您可能不仅会在不同的机器上获得不同的代码,甚至在同一台机器上的不同运行中也会如此,在同一次运行中的不同时间也会如此。 - gidds
7个回答

82

普通用法

正如其他答案所解释的那样,您可以使用以下JVM选项运行:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

筛选特定方法

你也可以使用以下语法来筛选特定方法:

-XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*MyClass.myMethod

注:

  • 根据操作系统等情况,您可能需要将第二个参数放入引号中。
  • 如果方法被内联,您可能会错过一些优化。

如何在Windows上安装所需的库

如果您正在运行Windows,则此页面提供了有关如何构建和安装hsdis-amd64.dllhsdis-i386.dll所需的说明。以下为参考内容:


获取预构建的二进制文件

您可以从fcml项目下载Windows的预构建二进制文件

如何在Windows上构建hsdis-amd64.dllhsdis-i386.dll

本指南版本是在Windows 8.1 64位上使用64位Cygwin准备的,并生成hsdis-amd64.dll。

  1. 安装Cygwin。在选择软件包屏幕上,添加以下软件包(通过展开Devel类别,然后单击每个包名称旁边的Skip标签来实现):

    • make
    • mingw64-x86_64-gcc-core(仅对hsdis-amd64.dll需要)
    • mingw64-i686-gcc-core(仅对hsdis-i386.dll需要)
    • diffutils(在Utils类别中)
  2. 运行Cygwin终端。可以使用安装程序创建的桌面或开始菜单图标完成,将创建您的Cygwin主目录(默认情况下为C:\ cygwin \ home \<username>\ C:\ cygwin64 \ home \<username>\)。

  3. 下载最新的GNU binutils源代码包并将其内容提取到Cygwin主目录。撰写本文时,最新包是binutils-2.25.tar.bz2。这应该会在您的Cygwin主目录中创建一个名为binutils-2.25(或任何最新版本)的目录。
  4. 通过转到JDK 8更新存储库,选择与已安装JRE版本相对应的标记,并单击bz2来下载OpenJDK源代码。将hsdis目录(位于中)提取到您的Cygwin主目录中。
  5. 在Cygwin终端中,输入cd ~/hsdis
  6. 要构建hsdis-amd64.dll,请输入

    make OS=Linux MINGW=x86_64-w64-mingw32 'AR=$(MINGW)-ar'


1
其他平台的预编译二进制文件 - https://kenai.com/projects/base-hsdis/downloads - Ashwin Jayaprakash
@AshwinJayaprakash 我应该把这些文件放在 Mac OS 的哪里? - Koray Tugay
@KorayTugay 把它们放在 /usr/lib/ 中。 - Jean-François Savard
我已经更新了答案,通过从链接页面的最新版本复制,但这突显了我们通常链接到外部资源而不是逐字复制它们的原因。 - Aleksandr Dubinsky
@AleksandrDubinsky 感谢您的更新。我故意复制了它:如果那个网站被关闭,我的答案仍将是自包含的... - assylias
请注意,不要使用 jpbempel.blogspot.com/2013/07/how-to-build-hsdis-amd64dll.html 这个指南尝试使用 MSYS/MinGW 构建 hsdis。它行不通。 - Aleksandr Dubinsky

46
假设您使用的是Sun Hotspot JVM(即由Oracle在java.com上提供的JVM),则可以添加以下标志:

-XX:+PrintOptoAssembly

运行代码时,这将打印JIT编译器生成的优化代码并省略其余部分。

如果要查看包括未经优化的部分在内的整个字节码,请添加以下内容:

-XX:CompileThreshold=#

运行代码时。

您可以在此处阅读有关此命令和JIT功能的更多信息。


这个选项只存在于调试版本中吗?因为我的JVM(“Java(TM) SE Runtime Environment (build 1.6.0_16-b01”)无法识别它,尽管网上的源代码表明此功能在Sun Java 6和OpenJDK中可用。 - Joachim Sauer
2
需要DEBUG二进制文件。http://blogs.warwick.ac.uk/richardwarburton/entry/hotspot_print_assembly/ - alsor.net
3
现在不应该是用-XX:+PrintAssembly吗?至少现在是这样的吧?我在我的机器上测试过了,与这里所说的相符: http://wikis.sun.com/display/HotSpotInternals/PrintAssembly 在使用此选项之前,需要使用-XX:+UnlockDiagnosticVMOptions以及反汇编插件。 - Blaisorblade
@Blaisorblade 我得到了以下错误信息:未正确指定VM选项“PrintAssembly” 错误:无法创建Java虚拟机。 错误:发生了致命异常。程序将退出。 - Koray Tugay
1
@KorayTugay 请查看其他答案 - 更新后的链接为https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly,如https://dev59.com/53I_5IYBdhLWcg3wJfkM#15146962或https://dev59.com/53I_5IYBdhLWcg3wJfkM#4149878所示。如果按照说明操作无法正常工作,请在适当的位置(不确定是否应该引用此问题的另一个问题)提出详细信息。 - Blaisorblade

31
您需要一个hsdis插件来使用PrintAssembly。一个方便的选择是基于FCML库的hsdis插件。
它可以编译为类UNIX系统,在Windows上,您可以使用在Sourceforge的FCML download部分提供的预构建库:

在Windows上安装:

  • 提取dll(可以在hsdis-1.1.2-win32-i386.zip和hsdis-1.1.2-win32-amd64.zip中找到)。
  • 将dll复制到存在java.dll的任何位置(使用Windows搜索)。 在我的系统上,我在两个位置找到了它:
    • C:\Program Files\Java\jre1.8.0_45\bin\server
    • C:\Program Files\Java\jdk1.8.0_45\jre\bin\server

在Linux上安装:

  • 下载源代码并解压
  • cd <源代码目录>
  • ./configure && make && sudo make install
  • cd example/hsdis && make && sudo make install
  • sudo ln -s /usr/local/lib/libhsdis.so <JDK路径>/lib/amd64/hsdis-amd64.so
  • sudo ln -s /usr/local/lib/libhsdis.so <JDK路径>/jre/lib/amd64/hsdis-amd64.so
  • 在我的系统上,JDK位于/usr/lib/jvm/java-8-oracle

如何运行:

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 
-XX:+LogCompilation -XX:PrintAssemblyOptions=intel,mpad=10,cpad=10,code 
-jar fcml-test.jar

额外的配置参数:

code 在助记符之前打印机器代码。
intel 使用Intel语法。
gas 使用AT&T汇编器语法(GNU汇编器兼容)。
dec 以十进制值打印IMM和位移。
mpad=XX 指令助记符部分的填充。
cpad=XX 机器代码的填充。
seg 显示默认段寄存器。
zeros 在HEX字面量的情况下显示前导零。

在Windows上,Intel语法是默认语法,而在GNU/Linux上,AT&T语法是默认语法。

有关更多详细信息,请参见FCML库参考手册


感谢您修复这个库。它在Linux上也很好用。我将删除我的旧评论以保持整洁。 - Aleksandr Dubinsky
在Linux上,我安装了libhsdis.so并创建了一个指向hsdis-amd64.so的软链接。然后我运行java命令,它提示找不到hsdis-amd64.so。我重启后再次运行java,就可以了。如何避免重新启动以使软链接立即生效?注销? - gfan
2
只是一个小补充:在某些Linux发行版上,您可以只需安装一个软件包,例如在Ubuntu中:apt-get install libhsdis0-fcml(https://askubuntu.com/a/991166/489909)。自己构建可能并不必要。 - David Georg Reichelt

8

看起来你回答中的链接已经失效了,能否请您更新一下呢?非常感谢。 - lin

5

如果您在Windows机器上运行它,我相信WinDbg会非常有帮助。

我刚刚运行了一个jar文件。

  • 然后通过Windbg连接到Java进程。
  • 通过~命令检查线程; 有11个线程,0个线程是主工作线程。
  • 切换到0线程 - ~0s
  • 通过kb查看未管理的调用堆栈,其中包括:

    0008fba8 7c90e9c0 ntdll!KiFastSystemCallRet
    0008fbac 7c8025cb ntdll!ZwWaitForSingleObject+0xc
    0008fc10 7c802532 kernel32!WaitForSingleObjectEx+0xa8
    0008fc24 00403a13 kernel32!WaitForSingleObject+0x12
    0008fc40 00402f68 java+0x3a13
    0008fee4 004087b8 java+0x2f68
    0008ffc0 7c816fd7 java+0x87b8

    0008fff0 00000000 kernel32!BaseProcessStart+0x23

加粗的行是在JVM上直接执行JIT代码的结果。

  • 接下来我们可以查找方法地址:
    java+0x2f68是00402f68

  • 在WinDBG上:
    点击查看 -> 反汇编。
    点击编辑 --> 转到 地址。
    放入00402f68,然后获得

    00402f68 55 push ebp
    00402f69 8bec mov ebp,esp
    00402f6b 81ec80020000 sub esp,280h
    00402f71 53 push ebx
    00402f72 56 push esi
    00402f73 57 push edi
    ... 以此类推

如果需要其他信息,请参阅示例,了解如何使用进程资源管理器和WinDbg从内存转储中追溯JIT代码。


4

另一种查看机器代码和性能数据的方法是使用AMD的CodeAnalyst或OProfile,它们具有用于将执行的Java代码可视化为机器代码的Java插件。


1
使用JMH的perfasm性能分析器(LinuxPerfAsmProfilerWinPerfAsmProfiler)打印您的热点代码的汇编信息。由于其依赖于PrintAssembly,因此JMH需要hsdis库。保留HTML标签。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接