OSR is a compiler optimization technique on Java Hotspot VM. The Java’s significant features is Portability meaning that it can run on any OS.
Let’s reiterate the overview of how the compiler works,
- When the javac is invoked, compiler takes java code and compiles into byte code, i.e. Demo.java into Demo.class.
- The byte code can be translated into Hardware Instructions by JVM.
The translation works into two ways:
- Interpretation -It converts the Java code into machine code, and start execute it. It works as dictionary approach, checks instruction sets to execute machine code.
- Compilation -It executes faster and more optimized but takes slower compilation time.
Static and Dynamic Compilation
- Static Compilation also called Ahead-Of-Time (AOT). Most complex works happens before executing the code. It directly converts Source to Native Code.
- Dynamic Compilation, Modern Java uses Just-In-Time (JIT) for that. Most of the compilation work happens during the execution time.
Generally Java Hotspot VM has two JIT compilers, known as C1 and C2.
- Client compiler called C1. -It compiles quick but less optimized and used for quick startup such as GUI applications.
- Server compiler called C2. It takes slower compilation time, but more optimized code. It’s predominantly used for long running Server-Side applications.
These compilers are written C/C++ programming language, and new GraalVM Compiler are written in pure Java. It’s not covered in the blog.
Basically the output of the both compilers can be different for same method, and the modern Java applications uses both(C1 + C2) with the help of “Tiered Compilation”. Usually it takes the C1’s profiling used for C2’s compilation.
Since from JDK8, this feature is default. It uses the C1 first for better performance startup and once the application warmed up properly, C2 take over the rest for faster executions.
The below snippet is a simple hello world program, and run it with the below JVM flag, note that since from JDK9 NOT required to use javac first to compile, it runs directly with java command only.
java -XX:+PrintFlagsFinal JVMCompilerSample.java
The highlighted shows default usage of “Tiered Compilation”, and it can be disabled by setting the -XX:-TieredCompilation flag,
java -XX:-TieredCompilation -XX:+PrintFlagsFinal JVMCompilerSample.java
The tiered compilation could be controlled by setting the below flag whether to use C1 or C2,
-XX:TieredStopAtLevel=<number 0 to 4>
There are five flag options available,
- 0 -Uses ONLY interpreter
- 1 -C1 Compilation with full optimization without profiling
- 2 -C1 Compilation with invocation and back edge counters
- 3 -C1 Compilation with full profiling
- 4 -Both C1 and C2 compilation
The compilation time is measured with all the four flags in the sample hello world program, and used JMX API to get compilation time, and the result shows interpreter takes ZERO time because it just translates code to machine code and runs slower on hardware than JIT code, and JIT takes extra time for aforesaid processes, and runs faster on the hardware.
-Xcomp -XX:TieredStopAtLevel=<number 0 to 4>
OSR replaces long running interpreted “hot” byte code by it’s compiled version when it becomes available. It’s a four step process,
- The runtime extracts the execution state (current variable values and program counter) from a particular method
- The compilation system then recompiles the method
- Compiler generates a stack activation frame and updates it with the values extracted from the previous version
- The system replaces the old activation frame with the new and restarts execution in the new version.
The simple hello world program ran and check the output with the following commands, set JVM flags and log the output in a file, the below snippet shows it’s not used by OSR technique,
java -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:LogFile=/<file location>/out.log JVMCompilerSample.java
The same above program slightly modified to make use of OSR technique, created a long running for loop as below in the snippet, the program executed with the same command as mentioned above.
The highlighted flags are,
bytes = ‘27’ -Size of the method
compile_kind = ‘osr’ -Shows that it is used OSR technique
compiler = ‘c1’ -Shows C1 compiler used for better performance in this case
count = ‘1’ -It’s an invocation count used for trigger compilers
backedge_count = <number> -It is used to detect methods that contain loops, and incremented by interpreter.
iicount = ‘1’ -It is an interpreter invocation count.
OSR’s main advantage is on-fly compilation, and optimization for native code running on hardware.