Escape analysis and inlining
The JIT inlines small methods, then proves some objects never escape and skips allocating them entirely.
Open this lesson in the learning hubKey points
- Inlining copies a small method body into its caller. That removes the call, and lets later optimisations see across the boundary.
- It is why tiny getters cost nothing once warm, and why enormous methods optimise badly - they blow past the inlining size limits.
- After inlining, escape analysis asks a simple question: can this object ever be seen outside the method that made it?
- If it cannot escape, the JIT does scalar replacement. The fields become CPU registers and the object is never allocated at all.
- It also elides locks on objects only one thread can see. Both optimisations are on by default; you do not need flags.
- Store it in a field, return it, or pass it to a method too big to inline, and it escapes. Then it costs full price.
Example
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
public class Main {
static class Vec {
final double x, y;
Vec(double x, double y) { this.x = x; this.y = y; }
double len() { return Math.sqrt(x * x + y * y); }
}
static Vec escapee; // a global handle forces a real heap object
static double sumLocal(int n) { // the Vec never leaves this method
double s = 0;
for (int i = 0; i < n; i++) { s += new Vec(i, i + 1).len(); }
return s;
}
static double sumEscaping(int n) { // the Vec is published to a static field
double s = 0;
for (int i = 0; i < n; i++) {
Vec v = new Vec(i, i + 1);
escapee = v;
s += v.len();
}
return s;
}
public static void main(String[] args) {
double keep = 0;
for (int i = 0; i < 200; i++) { keep += sumLocal(10_000) + sumEscaping(10_000); }
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
if (bean instanceof com.sun.management.ThreadMXBean sun
&& sun.isThreadAllocatedMemorySupported()) {
long a0 = sun.getCurrentThreadAllocatedBytes();
keep += sumLocal(2_000_000);
long a1 = sun.getCurrentThreadAllocatedBytes();
keep += sumEscaping(2_000_000);
long a2 = sun.getCurrentThreadAllocatedBytes();
System.out.println("2,000,000 Vec objects written in the source, both times.");
System.out.println("bytes allocated, non-escaping : " + (a1 - a0));
System.out.println("bytes allocated, escaping : " + (a2 - a1));
System.out.println("The first lot mostly never became heap objects at all.");
} else {
System.out.println("this VM does not expose per-thread allocation counters");
}
System.out.println("checksum " + (long) keep);
}
}
Short methods and short-lived objects are exactly what the JIT rewards.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JVM course, and every lesson in it is listed on the JVM contents page.