HotSpot generates lots of machine code on-the-fly (including the interpreter and the JITted code). This page describes how you can disassemble such code so you can understand how it is generated and what it does.

hsdis

First, you need an external shared library called hsdis.

With the jdk/jdk repo (as of version 00ebc17f3cc6), you can build hsdis using the following on Linux/x64:

cd src/utils/hsdis
mkdir build
cd build
wget http://ftp.gnu.org/gnu/binutils/binutils-2.19.1.tar.bz2
tar jxf binutils-2.19.1.tar.bz2
ln -s binutils-2.19.1 binutils
cd ..
make ARCH=amd64 CFLAGS="-w -fPIC"
export LD_LIBRARY_PATH=$(pwd)/build/linux-amd64

This would generate hsdis-amd64.so. Remember you need to include the path to this file in your LD_LIBRARY_PATH so HotSpot can pick it up.

For building hsdis on other platforms, see here.

Disassembling the HotSpot Interpreter

The HotSpot interpreter is generated dynamically at VM start-up. Its contents can depend on the command-line options (such as -XX:+TraceBytecodes). Also, non-product JVMs will include lots of debugging and assertion code in the interpreter, which may be distracting.

In general, if you’re just curious of how the interpreter handles a particular Java bytecode, your best bet is to use the product JVM.

For example, iconst_5 is implemented like this in templateTable_x86.cpp

void TemplateTable::iconst(int value) {
  transition(vtos, itos);
  if (value == 0) {
    __ xorl(rax, rax);
  } else {
    __ movl(rax, value);
  }
}

You can see the assembler listing with the following command. Note that most of the code is just boiler-plate for setting up the interpreter states, and the actual bytecode implementation is just a single mov instruction.

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintInterpreter -version
[ .... snip .... ]
iconst_5  8 iconst_5  [0x00007f09a066e180, 0x00007f09a066e1e0]  96 bytes

  0x00007f09a066e180: push   %rax
  0x00007f09a066e181: jmpq   0x00007f09a066e1bf
  0x00007f09a066e186: sub    $0x8,%rsp
  0x00007f09a066e18a: vmovss %xmm0,(%rsp)
  0x00007f09a066e18f: jmpq   0x00007f09a066e1bf
  0x00007f09a066e194: sub    $0x10,%rsp
  0x00007f09a066e198: vmovsd %xmm0,(%rsp)
  0x00007f09a066e19d: jmpq   0x00007f09a066e1bf
  0x00007f09a066e1a2: sub    $0x10,%rsp
  0x00007f09a066e1a6: mov    %rax,(%rsp)
  0x00007f09a066e1aa: mov    $0x0,%r10
  0x00007f09a066e1b4: mov    %r10,0x8(%rsp)
  0x00007f09a066e1b9: jmpq   0x00007f09a066e1bf
  0x00007f09a066e1be: push   %rax
  0x00007f09a066e1bf: mov    $0x5,%eax  //store integer 5 to the top of stack
  0x00007f09a066e1c4: movzbl 0x1(%r13),%ebx
  0x00007f09a066e1c9: inc    %r13
  0x00007f09a066e1cc: mov    $0x7f09be03c440,%r10
  0x00007f09a066e1d6: jmpq   *(%r10,%rbx,8)
  0x00007f09a066e1da: nopw   0x0(%rax,%rax,1)

Up to JDK 11, the -XX:+PrintInterpreter output is most uncommented so it’s hard to find the C code that generates the assembler code. I am working on a patch JDK-8204267 which generate extra comments to show how the code is generated (the following output has been edited for clarity):

ifeq  153 ifeq  [0x00007f830cc93da0, 0x00007f830cc941c0] 1056 bytes
[ .... snip .... ]
mov    (%rsp),%eax
add    $0x8,%rsp
test   %eax,%eax           ;;@FILE: /src/hotspot/cpu/x86/templateTable_x86.cpp
jne    0x00007f830cc94177  ;; 2354:   __ jcc(j_not(cc), not_taken);
mov    -0x18(%rbp),%rcx    ;; 2120:   __ get_method(rcx); // rcx holds method
mov    -0x28(%rbp),%rax    ;; 2121:   __ profile_taken_branch(rax, rbx); // rax holds updated MDP, rbx
test   %rax,%rax
je     0x00007f830cc93dd8
mov    0x8(%rax),%rbx
add    $0x1,%rbx
sbb    $0x0,%rbx
mov    %rbx,0x8(%rax)
add    0x10(%rax),%rax
mov    %rax,-0x28(%rbp)
movswl 0x1(%r13),%edx      ;; 2133:     __ load_signed_short(rdx, at_bcp(1));
bswap  %edx                ;; 2135:   __ bswapl(rdx);
sar    $0x10,%edx          ;; 2138:     __ sarl(rdx, 16);
movslq %edx,%rdx           ;; 2140:   LP64_ONLY(__ movl2ptr(rdx, rdx));
add    %rdx,%r13           ;; 2164:   __ addptr(rbcp, rdx);
test   %edx,%edx           ;; 2179:     __ testl(rdx, rdx);             // check if forward or backward branch
jns    0x00007f830cc93eec  ;; 2180:     __ jcc(Assembler::positive, dispatch); // count only if backward branch
mov    0x18(%rcx),%rax     ;; 2184:     __ movptr(rax, Address(rcx, Method::method_counters_offset()));
test   %rax,%rax           ;; 2185:     __ testptr(rax, rax);
jne    0x00007f830cc93ead  ;; 2186:     __ jcc(Assembler::notZero, has_counters);
push   %rdx                ;; 2187:     __ push(rdx);
push   %rcx                ;; 2188:     __ push(rcx);
callq  0x00007f830cc93e09  ;; 2189:     __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::build_method_counters),