Just In Time Compilation & the Code Cache

The Java code is compiled into Bytecode using the Java compiler. The Bytecode is then interpreted by the JVM for execution. This gives Java a powerful feature called "Write Once, Run Anywhere". The JVM is not limited to Java. Since it does not execute Java source code directly but rather Bytecode, it can also interpret Bytecode produced by other JVM-based languages, such as Kotlin and Scala.

Bytecode Interpretation

Initially the JVM acts as any other interpreter i.e. interpreting each line of code as it is needed. However by default it will make the code execution slow, compared to languages that compiles code directly to machine native code such as C. Since, the native code doesn’t need any additional software to interpret the code It makes it faster to run. But, as a downside we lose the Write Once Run Anywhere feature, since the compiled code is native to the underlying OS.

JIT Compilation

To overcome the slowness of bytecode interpretation, JVM has a feature called Just In Time Compilation or JIT Compilation. The JVM will monitor the code blocks which are frequently executed and then it converts them to the native machine code. Doing so will make the execution faster along the native machine code blocks.

The implication of this is, if the code is left to run for longer period of time then it will be executed faster. For example a method that runs every few minutes will be compiled to the native machine code making the execution faster. JVM is a multi-threaded program. The process of compiling the bytecode to native machine code runs on a separate thread. Hence the normal process of JVM is not affected by the JIT Compilation.

<aside> 📌

The native code is the code which can be directly understood by the OS.

</aside>

Java Options

We can verify JIT Compilation process using Java Options, In the following command we have added a boolean flag which will print the information about the JIT Compilation during the program execution. —

java -XX:+PrintCompilation Main

In Java, most of the flags are written in the same way. They start with a dash -XX which shows that it is an advanced option. It is then followed by a plus + or minus - which indicates if the option is turned on or off. This is only possible for the boolean flags. Note, the flags are case sensitive. The snippet of the output of the above command is as follows —

 85   1     3       java.lang.String::hashCode (55 bytes)
 86   2 %   4       MyApp::processData @ 25 (100 bytes)
 87   3     1       java.lang.Math::sin (intrinsic)
 88   4 s   3       com.example.MyClass::init (30 bytes)
 89   5 n   0       com.example.NativeWrapper::nativeMethod (native)
 90   6 !   3       com.example.MyClass::buggyMethod (60 bytes)

It printed information in multiple columns, let’s analyze each of the column —