Method Inlining in the JVM
What is Method Inlining?
Method inlining is a runtime optimization technique where the JVM replaces method calls with the actual method body. Instead of jumping to another location in memory to execute a method, the code is executed directly inline, reducing overhead.
This optimization is handled by the Just-In-Time (JIT) compiler, not the traditional Java compiler (javac). This means that even old Java code can benefit from improved inlining optimizations in newer JVM versions without recompilation.
How Does It Work?
The JIT compiler uses two main criteria to decide whether to inline a method:
- Frequency: The method must be “hot” — called frequently enough (default is 10,000 times)
- Size: The method must be small enough (measured in bytecode instructions)
Methods that are static
, private
, or final
are prime candidates for inlining. public
methods can also be inlined, but only if the JVM can determine there’s exactly one implementation.
Simple Example
Consider this basic example:
class Calculator {
private static int add(int a, int b) {
return a + b;
}
public void calculate() {
int sum = 0;
for (int i = 0; i < 10000; i++) {
sum = add(sum, i); // This method call is a candidate for inlining
}
}
public static void main(String[] args) {
Calculator calc = new Calculator();
// Run multiple times to give JIT more data
for (int i = 0; i < 100; i++) {
calc.calculate();
}
}
}
After sufficient executions, the JIT compiler might transform this into something conceptually similar to:
public void calculate() {
int sum = 0;
for (int i = 0; i < 20000; i++) {
sum = sum + i; // Inlined version - no method call overhead
}
}
Monitoring Inlining
To see which methods are being inlined, you can use these JVM flags:
java -XX:+PrintCompilation \
-XX:+UnlockDiagnosticVMOptions \
-XX:+PrintInlining \
Calculator
The output will show:
inline (hot)
— Method is frequently called and has been inlinedtoo big
— Method is too large to inlinehot method too big
— Method is frequently called but too large to inlinemade not entrant
— messages indicate that older versions of the compiled code are being replaced with newer, more optimized versions - this is the JIT compiler at work, continuously improving the code based on runtime information
Here is a sample output that shows the add
method has been inlined:
java -XX:+PrintCompilation \
-XX:+UnlockDiagnosticVMOptions \
-XX:+PrintInlining \
Calculator
64 1 3 java.lang.Object::<init> (1 bytes)
66 2 3 java.lang.String::coder (15 bytes)
68 3 3 java.lang.String::hashCode (60 bytes)
@ 17 java.lang.String::isLatin1 (19 bytes) inline
@ 27 java.lang.StringLatin1::hashCode (52 bytes) callee is too large
@ 37 java/lang/StringUTF16::hashCode (not loaded) not inlineable
68 5 3 Calculator::add (4 bytes)
69 4 3 java.lang.String::length (11 bytes)
69 6 4 Calculator::add (4 bytes)
@ 6 java.lang.String::coder (15 bytes) inline
69 5 3 Calculator::add (4 bytes) made not entrant
69 7 % 3 Calculator::calculate @ 4 (24 bytes)
@ 13 Calculator::add (4 bytes) inline
70 8 3 Calculator::calculate (24 bytes)
@ 13 Calculator::add (4 bytes) inline
70 9 % 4 Calculator::calculate @ 4 (24 bytes)
70 7 % 3 Calculator::calculate @ 4 (24 bytes) made not entrant
@ 13 Calculator::add (4 bytes) inline (hot)
70 10 4 Calculator::calculate (24 bytes)
70 8 3 Calculator::calculate (24 bytes) made not entrant
@ 13 Calculator::add (4 bytes) inline (hot)
Understanding -XX:+PrintCompilation
First column (64, 68, …)
Timestamp (milliseconds since JVM start).
Second column (1,2, …)
Compilation ID (unique identifier for this compilation task).
Third column (3,4)
Compilation level:
0
- Interpreter1
- C1 compilation with no profiling2
- C1 compilation with light profiling3
- C1 compilation with full profiling4
- C2 compilation (server compiler, highest optimization)
Fourth column
Method name and size.
Additional markers
%
- OSR compilation (On-Stack Replacement)s
- Method is synchronized!
- Method has exception handlersb
- Blocking compilationn
- Native methodmade not entrant
- Method version is no longer used
Best practices
Don’t manually inline code
- Let the JVM handle inlining
- Manual inlining makes code harder to maintain
Keep methods focused and small
- Break large methods into smaller, logical pieces
- This makes them better candidates for inlining
Avoid preventing inlining
- Use
final
classes when inheritance isn't needed - Minimize use of complex inheritance hierarchies
Don’t modify default JVM inlining settings
- The default values are well-tuned for most applications
- Only change them if you have concrete evidence of benefit
Advanced Optimization Tips
If you find methods marked as “hot method too big”, consider:
- Breaking complex conditional logic into separate methods
- Splitting large loops into smaller operations
- Extracting complex switch statements into separate methods
Remember
Code readability and maintainability should always be prioritized over manual optimization attempts. The JVM’s inlining mechanism is sophisticated enough to handle most optimization needs automatically.