What is scalar replacement, how does it differ from stack allocation, and why does it make many small short-lived objects effectively free?
answer
- Scalar replacement = explode object into field-sized locals, no object
- Stack allocation = whole object on stack (C2 largely doesn't do it)
- Deletes allocation + indirection + GC cost → effectively free
- Only for NoEscape objects; gated by -XX:+EliminateAllocations
- Fields become normal locals → optimizer folds/registers them
basics
~20 sScalar replacement means the JIT takes a non-escaping object and replaces it with its individual fields, held in CPU registers or on the stack, so the object is never actually created. There is no heap memory and nothing for the garbage collector to clean up, so the object costs almost nothing.
solid answer
~50 sScalar replacement is the optimization the JIT applies to a NoEscape object: instead of allocating the object and accessing its fields through a reference, it "explodes" the object into its constituent scalar fields and keeps those as individual local values in registers or stack slots. The object as such ceases to exist — no header, no heap memory, no reference indirection. Stack allocation is the related but weaker idea of putting the whole object (header and all) on the stack instead of the heap; HotSpot's C2 in practice prefers scalar replacement and does not do general true stack allocation. Scalar replacement makes small short-lived objects effectively free because their only costs — heap allocation, field-access indirection, and later GC tracing — all disappear; what remains is a handful of register/stack values, which the rest of the optimizer (register allocation, constant folding, dead-code elimination) then handles like any other locals. The catch is that it only applies when escape analysis proves NoEscape, which is fragile.
go deeper
Can say the JVM can skip creating some objects, replacing them with plain values, so they cost almost nothing.
Defines scalar replacement as breaking the object into its fields and notes it removes heap allocation and GC cost.
Distinguishes scalar replacement from stack allocation, knows C2 uses the former, and ties the 'effectively free' result to removing allocation+indirection+GC plus downstream optimizer composition.
Reasons about when it pays off on a real hot path, how it composes with register allocation/inlining, the controlling flags, and how to verify it actually fired rather than assume.
## Setup: what an object normally costs When you write `new Point(x, y)`, the JVM normally: 1. allocates a block on the **heap** (shared, GC-managed memory), including an **object header** (bookkeeping bytes the JVM keeps on every object), 2. stores the fields (`x`, `y`) inside it, 3. hands you a **reference** (a pointer) to it, and 4. later the **garbage collector** must trace and reclaim it. Accessing `point.x` means dereferencing the pointer to reach the field. For a tiny object you use once, all of this is overhead. ## Scalar replacement A **scalar** is a single primitive-like value (an `int`, a reference, etc.) — as opposed to an aggregate object made of several fields. **Scalar replacement** is the JIT optimization that, once **escape analysis** has proven an object is **NoEscape** (never leaves the method, never seen by another thread), *replaces the object with its individual fields*. The compiler rewrites the code so that `Point p = new Point(x,y); ... p.x ... p.y ...` becomes, in effect, two ordinary local variables holding `x` and `y`. The object is never allocated; there is no header, no heap block, no reference, no indirection. After scalar replacement the fields are just normal locals, so the rest of the optimizer treats them like any other values: it can keep them in CPU registers, constant-fold them, or delete them entirely if unused (dead-code elimination). This composition is the real power — removing the object turns object-level code into plain scalar code the optimizer already handles superbly. ## Stack allocation — the related, weaker idea **Stack allocation** means putting the *whole* object — header and all — on the **stack** (the per-call scratch region freed automatically when the method returns) instead of the heap. It still keeps the object's identity and layout; it just changes where it lives, so allocation is cheap and there is no GC involvement. It is conceptually simpler than scalar replacement but keeps more of the object's costs (header, indirection). In HotSpot, **C2 does not perform general true stack allocation**; it relies on scalar replacement instead, which is strictly more aggressive when it applies. So in interviews: scalar replacement = decompose into fields (no object at all); stack allocation = object still exists but on the stack (a fallback HotSpot largely doesn't use). ## Why short-lived objects become effectively free The cost of a temporary object is the sum of (a) heap allocation, (b) field-access indirection, and (c) eventual GC tracing/reclamation. Scalar replacement deletes **all three**: with no object, there is nothing to allocate, no pointer to chase, and nothing for the GC to see. What remains is a few register/stack values — essentially the cost of using a couple of local variables, which is negligible. This is why idiomatic Java that spins up many tiny helpers (iterators, `Optional`s, boxed values, small value-like objects) can still be fast: the JIT erases most of them in hot code. ## The catch: it is fragile Scalar replacement is *only* legal for NoEscape objects. The moment the object escapes — returned, stored in a field, thrown, passed to a method the compiler can't inline, or published to another thread — the optimization cannot apply and you pay the full allocation + GC cost. It also requires the allocation site to be in JIT-compiled, sufficiently-inlined hot code. So the optimization is real and powerful but easily defeated by small code changes, which is why you shouldn't depend on it for correctness, only enjoy it for performance. ## Controlling/observing it Flags: `-XX:+DoEscapeAnalysis` (escape analysis, default on) gates `-XX:+EliminateAllocations` (scalar replacement, default on) and `-XX:+EliminateLocks` (lock elision). You can confirm it fired by inspecting C2 output (e.g. with diagnostic JIT logging) rather than guessing.
- After scalar replacement, where do the object's former fields live?As individual local values in CPU registers or stack slots, handled by the optimizer like any other locals — there is no object and no heap memory.
- Which is more aggressive, scalar replacement or stack allocation, and which does HotSpot's C2 actually use?Scalar replacement is more aggressive (the object disappears entirely). C2 uses scalar replacement; it does not perform general true stack allocation.
saying these in an interview costs you the question
- Saying HotSpot stack-allocates non-escaping objects — it scalar-replaces them; true general stack allocation is not done by C2
- Claiming scalar replacement works regardless of escape state
- Thinking it reduces allocation cost but still creates the object — it removes the object entirely
- Assuming it applies in interpreted/cold code