Q37 of 40 · Core Java

What is escape analysis and stack allocation in the JVM?

Core JavaSeniorescape-analysisjitjvmperformancestack-allocationgc

Short answer

Short answer: Escape analysis is a JIT-compiler optimisation that determines whether an object created inside a method is accessible (escapes) outside that method's stack frame. If the object does not escape — i.e., it's not returned, stored in a field, or passed to another thread — the JIT can allocate it on the stack instead of the heap, or even replace it with scalar variables. Stack-allocated objects are freed when the frame pops, with zero GC pressure.

Detail

Three escape states

  • NoEscape — object stays local to one method: stack allocation or scalar replacement possible.
  • ArgEscape — object passed as argument to a method that doesn't let it escape further: synchronisation on it can be eliminated.
  • GlobalEscape — object reachable from a static field or returned/stored beyond the current method: must be heap-allocated normally.
// Example 1: loop-local point — likely stack-allocated by JIT
record Point(double x, double y) {}

void computeCentroid(double[] xs, double[] ys) {
    double sumX = 0, sumY = 0;
    for (int i = 0; i < xs.length; i++) {
        // Point does not escape this loop body (not stored anywhere)
        var p = new Point(xs[i], ys[i]);  // JIT may scalar-replace this
        sumX += p.x();
        sumY += p.y();
    }
}
import java.util.ArrayList;
import java.util.List;

// Example 2: object escapes — heap allocation required
List<Point> points = new ArrayList<>();
void processAll(double[] xs, double[] ys) {
    for (int i = 0; i < xs.length; i++) {
        var p = new Point(xs[i], ys[i]);
        points.add(p);   // p escapes into the list — heap allocated
    }
}

Lock elision (bonus optimisation enabled by escape analysis)

// StringBuffer is synchronized. If sb doesn't escape, JIT removes all locks.
String buildKey(String prefix, int id) {
    var sb = new StringBuffer();   // local, doesn't escape
    sb.append(prefix).append(id);  // JIT elides synchronisation entirely
    return sb.toString();          // string result escapes, not sb
}

Practical impact for QA automation

  • Short-lived helper objects (request builders, response wrappers) used only within a test method may be stack-allocated, reducing GC pauses in high-frequency test loops.
  • Avoid storing test helpers in instance fields unless necessary — it forces heap allocation and increases GC pressure.
  • Enabled by default since Java 6u23 (-XX:+DoEscapeAnalysis).

// WHAT INTERVIEWERS LOOK FOR

Understanding that the JIT can move heap allocations to the stack when objects don't escape. The three escape categories. Lock elision as a bonus optimisation. Strong answers mention scalar replacement (replacing an object with its component primitives) and JMH as the correct way to measure allocation impact.

// COMMON PITFALL

Assuming all `new` allocations go to the heap. Since Java 6, the JIT routinely eliminates heap allocations for non-escaping objects. Micro-benchmarks that allocate aggressively and expect GC pressure may see less than expected if escape analysis kicks in.