The HotSpot JVM provides several command-line arguments related to Just In Time (JIT) compilation. In this post, I look at the steps needed to start applying the command-line flag -XX:CompileCommand to see the just-in-time compilation being performed on individual methods.
Nikita Salnikov-Tarnovski's blog post Do you get Just-in-time compilation? provides a nice overview of the JIT compiler and why it's needed. The following is an excerpt of that description:
Welcome - HotSpot. The name derives from the ability of JVM to identify "hot spots" in your application's - chunks of bytecode that are frequently executed. They are then targeted for the extensive optimization and compilation into processor specific instructions. ... The component in JVM responsible for those optimizations is called Just in Time compiler (JIT). ... Rather than compiling all of your code, just in time, the Java HotSpot VM immediately runs the program using an interpreter, and analyzes the code as it runs to detect the critical hot spots in the program. Then it focuses the attention of a global native-code optimizer on the hot spots.
The IBM document JIT compiler overview also provides a concise high-level overview of the JIT and states the following:
In practice, methods are not compiled the first time they are called. For each method, the JVM maintains a call count, which is incremented every time the method is called. The JVM interprets a method until its call count exceeds a JIT compilation threshold. Therefore, often-used methods are compiled soon after the JVM has started, and less-used methods are compiled much later, or not at all. The JIT compilation threshold helps the JVM start quickly and still have improved performance. The threshold has been carefully selected to obtain an optimal balance between startup times and long term performance.
Identifying JIT-Compiled Methods
Because JIT compilation "kicks" in for a particular method only after it's been invoked and interpreted a number of times equal to that specified by -XX:CompileThreshold (10,000 for server JVM and 5,000 for client JVM), not all methods will be compiled by the JIT compiler. The HotSpot command-line option -XX:+PrintCompilation is useful for determining which methods have reached this threshold and have been compiled. Any method that has output displayed with this option is a compiled method for which compilation details can be gleaned using -XX:CompileCommand.
The following screen snapshot demonstrates using
-XX:+PrintCompilation to identify JIT-compiled methods. None of the methods shown are of the simple application itself. All methods runs enough times to meet the threshold to go from being interpreted to being compiled just-in-time are "system" methods.
-XX:CompileCommand Depends on
One of the prerequisites for using
-XX:CompileCommand to "print generated assembler code after compilation of the specified method" is to use -XX:+UnlockDiagnosticVMOptions to "unlock the options intended for diagnosing the JVM."
-XX:CompileCommand Depends on Disassembler Plugin
Another dependency required to run
-XX:CompileCommand against a method to view "generated assembler code" created by the JIT compilation is inclusion of the disassembler plugin. Project Kenai contains a Basic Disassembler Plugin for HotSpot Downloads page that can be used to access these, but Project Kenai is closing. The online resource How to build hsdis-amd64.dll and hsdis-i386.dll on Windows details how to build the disassembler plugin for Windows. Lukas Stadler documents the need for the disassembler plugin and provides a link to a "Windows x86 precompiled binary"
The easiest way I found to access a Windows-compatible disassembler plugin was to download it from the Free Code Manipulation Library (FCML) download page at http://fcml-lib.com/download.html. As of this writing, the latest version of download is fcml-1.1.1 (04.08.2015). The
hsdis-1.1.1-win32-amd64.zip can be downloaded for "An externally loadable disassembler plugin for 64-bit Java VM" and additional options for download are available as shown in the next screen snapshot.
The next screen snapshot demonstrates the error one can expect to see if this disassembler plugin has not been downloaded and placed in the proper directory.
The error message states, "Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled". There is a
hsdis-amd64.dll in the ZIP file
hsdis-1.1.1-win32-amd64.zip available for download from FMCL. Now, we just need to extract the
hsdis-amd64.dll file from the ZIP file and copy it into the appropriate JRE directory.
The disassembler plugin JAR needs to be placed in either the
jre/bin/client directories associated with the JRE that is applied when you run the Java launcher (
java). In my case, I know that my path is defined such that it gets Java executables, including the Java launcher, from a JRE based on what my
JAVA_HOME environment variable is set to. The next screen snapshot shows which directory that is and I can see that I'll need to copy the disassembler plugin JAR into the JDK's "jre" directory rather than into a non-JDK "jre" directory.
Knowing that my Java launcher (
java) is run out of the JDK's "jre" installation, I know that I need to copy the disassembler plugin JAR into the appropriate subdirectory under that. In my case, there is a "server" subdirectory and no "client" subdirectory, so I want to copy the disassembler plugin JAR into
Seeing JIT Compiled Method's Generated Assembler Code
With the disassembler plugin JAR copied into my JRE's
bin/server subdirectory, I am now able to include the command-line option
-XX:CompileCommand=print with a specific method name to see that method's generated assembler code upon JIT compilation. In my case, because my own simple application doesn't have any methods that get interpreted enough times to trigger JIT, I'll monitor a "system" method instead. In this case, I specify the option "
-XX:CompileCommand=print,java/lang/String.hashCode" to print out the generated assembler code for the String.hashCode() method. This is demonstrated in the next screen snapshot.
This screen snapshot includes several affirmations that we've got the necessary dependencies set appropriately to use
-XX:CompileCommand. These affirmations include existence of the messages, "Loaded disassembler from..." and "Decoding compiled method...". The mere existence of much more output than before and the presence of assembler code are obvious verifications of successful use of
-XX:CompilerCommand to print a method's generated assembler code.
Deciphering Assembly Code
At this point, the real work begins. The printed generated assembler code can now be analyzed and methods can potentially be changed based on this analysis. This type of effort, of course, requires knowledge of the assembler syntax.
A Side Note on
I have not covered the option -XX:+PrintAssembly in this post because it is rarely as useful to see all generated assembly code at once as it is to see assembly code for specifically selected methods. I like how Martin Thompson articulates the issue, "[Using
-XX:+PrintAssembly] can put you in the situation of not being able to see the forest for the trees."
The HotSpot JVM option
-XX:CompileCommand is useful for affecting and monitoring the behavior of the Just-in-Time compiler. This post has shown how to apply the option in a Windows environment with the "