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


84

有什么方法可以查看JIT在JVM中生成的本机代码吗?


您确定要查看JIT编译的(本机)代码,还是仅查看字节码?我问是因为,如果您真的想查看本机代码,在这里提出这个问题会引起一些疑问。而且,对不起,我也不知道这种工具。
gimpf

3
我想看一眼JIT编译的本机代码。当然,这不是我要做的事情,而是某种实验和调查工作。
Alsor.net

较小的框架挑战:现代JVM中使用的动态编译器不仅仅具有一个版本的编译代码;它可能从解释开始,然后编译方法或方法的一部分,然后可能在类被加载/卸载,使用模式改变或基于性能统计信息时多次对其进行重新编译。(我认为它甚至可以放弃编译的版本,返回到解释,如果这似乎是有益的。)所以,你可能不仅在不同的机器,甚至也不是在同一台机器上不同的运行得到不同的代码,但是在不同的时间相同的运行。
gidds

Answers:


45

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

-XX:+ PrintOptoAssembly

在运行代码时。这将打印出由JIT编译器生成的优化代码,而将其余部分省去。

如果要查看整个字节码,包括未优化的部分,请添加

-XX:CompileThreshold =#

当您运行代码时。

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


此选项仅在调试版本中存在吗?因为我的JVM(的“Java(TM)SE运行时环境(建立1.6.0_16-B01”)不承认它,即使在网络上源表示此功能的Sun Java 6和OpenJDK的可用。
约阿希姆·绍尔

2
是的,需要DEBUG二进制文件。blogs.warwick.ac.uk/richardwarburton/entry/…– Alsor.net
2009年

3
那不是(今天)-XX:+ PrintAssembly,至少现在不是吗?在我的机器上进行了测试,并且与此处所说的相符:wikis.sun.com/display/HotSpotInternals/PrintAssembly您需要在此选项之前加上-XX:+ UnlockDiagnosticVMOptions和反汇编程序插件。
Blaisorblade 2011年

@Blaisorblade我得到:VM选项'PrintAssembly'指定不正确错误:无法创建Java虚拟机。错误:发生致命异常。程序将会退出。
Koray Tugay,2015年

@KorayTugay查看其他答案-更新的链接是wikis.oracle.com/display/HotSpotInternals/PrintAssembly,如stackoverflow.com/a/15146962/53974stackoverflow.com/a/4149878/53974中所述。如果以下指示无效,请在适当的位置询问详细信息(不确定是否适合您的情况,请参考此问题)。
Blaisorblade 2015年

76

一般用法

如其他答案所述,您可以使用以下JVM选项运行:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly

过滤特定方法

您还可以使用以下语法筛选特定方法:

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

笔记:

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

如何:在Windows上安装所需的库

如果您运行的是Windows,则此页面包含有关如何构建和安装hsdis-amd64.dll以及hsdis-i386.dll使其工作所需的说明。我们在下面复制并扩展该页面的内容*,以供参考:


从哪里获取预构建的二进制文件

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

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

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

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

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

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

    make OS=Linux MINGW=x86_64-w64-mingw32 'AR=$(MINGW)-ar' BINUTILS=~/binutils-2.25

    要构建hsdis-i386.dll,请输入

    make OS=Linux MINGW=i686-w64-mingw32 'AR=$(MINGW)-ar' BINUTILS=~/binutils-2.25

    无论哪种情况,都请替换2.25为您下载的binutils版本。OS=Linux这是必要的,因为尽管Cygwin是一个类似Linux的环境,但是hsdis makefile无法如此识别它。

  7. 构建将失败,并显示消息./chew: No such file or directorygcc: command not found<Cygwin home directory>\hsdis\build\Linux-amd64\bfd\Makefile在诸如Wordpad或Notepad ++之类的文本编辑器中进行编辑,以将其更改为SUBDIRS = doc po(如果使用binutils 2.25,则为第342行)SUBDIRS = po。重新运行上一个命令。

该DLL现在可以通过从复制它被安装hsdis\build\Linux-amd64hsdis\build\Linux-i586您的JREbin\serverbin\client目录。您可以通过搜索在系统上找到所有此类目录java.dll

温馨提示:如果您更喜欢Intel ASM语法而不是AT&T,请-XX:PrintAssemblyOptions=intel在使用的任何其他PrintAssembly选项中指定。

*页面许可为知识共享


1
用于其他平台的预构建二进制文件-kenai.com/projects/base-hsdis/downloads
Ashwin Jayaprakash13年

@AshwinJayaprakash我应该在Mac OS中将这些文件放在哪里?
Koray Tugay 2015年

@KorayTugay把它们/usr/lib/
让·弗朗索瓦·Savard

我已经通过从链接到页面的最新版本进行复制来更新了答案,但这突出了我们通常链接到外部资源而不是逐字复制的原因。
Aleksandr Dubinsky

@AleksandrDubinsky感谢您的更新。我是故意复制的:如果该站点被
删除

29

您需要一个hsdis插件才能使用PrintAssembly。一个方便的选择是基于FCML库的hsdis插件。

可以针对类似UNIX的系统对其进行编译,并且在Windows上,可以使用Sourceforge的FCML下载部分中提供的预构建库:

要在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 <source code dir>
  • ./configure && make && sudo make install
  • cd example/hsdis && make && sudo make install
  • sudo ln -s /usr/local/lib/libhsdis.so <JDK PATH>/lib/amd64/hsdis-amd64.so
  • sudo ln -s /usr/local/lib/libhsdis.so <JDK PATH>/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在十六进制文字的情况下显示前导零。

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

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


感谢您修复lib。在Linux上也很好用。我正在删除旧评论以保持混乱。
Aleksandr Dubinsky

在Linux上,安装libhsdis.so并建立到hsdis-amd64.so的软链接后,我运行java命令,但无法找到hsdis-amd64.so。我重新启动,然后重新运行Java,没关系。如何避免重新启动以使软链接立即工作?登出?
gfan

2
补充一点:在某些Linux发行版中,您可以仅安装一个软件包,例如在Ubuntu中:apt-get install libhsdis0-fcmlaskubuntu.com/a/991166/489909)。您自己构建此文件可能不是必需的。
David Georg Reichelt,


5

我相信WinDbg如果在Windows计算机上运行它会有所帮助。我刚一罐。

  • 然后我通过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的+ 0×12
    0008fc40 00402f68的java + 0x3a13
    0008fee4 004087b8的java + 0x2f68
    0008ffc0 7c816fd7的java + 0x87b8

    0008fff0 00000000 KERNEL32!BaseProcessStart + 0×23

突出显示的行是在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代码可视化为机器代码。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.