skip to content

What is lock elision (biased toward synchronization removal) via escape analysis, and when can the JIT safely drop a synchronized block?

level: seniorimportance: nice to knowfreq 22%

answer

  1. Lock elision = JIT drops synchronized when lock object is NoEscape
  2. Safe because no other thread can ever see the object → lock guards nothing
  3. Canonical: local StringBuffer's synchronized appends elided
  4. -XX:+EliminateLocks (gated by DoEscapeAnalysis)
  5. Not coarsening (merge) and not biased locking (different mechanisms)

basics

~20 s

Lock elision is when the JIT removes the locking from a synchronized block because escape analysis proves the lock object can only ever be touched by one thread. If no other thread can see the object, the lock protects nothing, so it is safe to delete.

solid answer

~50 s

Lock elision (synchronization elision) is an escape-analysis-driven optimization. A `synchronized` block exists to coordinate multiple threads accessing shared state. If escape analysis proves that the object being locked is NoEscape — it never leaves the creating method and is never published to another thread — then no other thread can ever contend for that lock, so the synchronization is provably useless. The JIT then elides it: it removes the monitor enter/exit, keeping only the body. The classic case is a local `StringBuffer` (whose methods are synchronized) or a `Vector`/`Collections.synchronizedList` used entirely within one method: the locks cost CPU and memory-barrier overhead but guard nothing, and the compiler strips them. It is controlled by `-XX:+EliminateLocks` (on by default, gated by `-XX:+DoEscapeAnalysis`). The safety condition is single-thread visibility; the moment the lock object could be shared, elision is illegal and is not applied.

go deeper

for a junior

Can say the JVM can skip locking when only one thread could ever use the object.

for a middle

Explains that escape analysis proving single-thread visibility lets the JIT drop a synchronized block, with the local StringBuffer example.

for a senior

States the safety condition precisely (NoEscape ⇒ no other thread can contend), names the flag, and distinguishes elision from coarsening and biased locking.

for a principal

Reasons about when relying on it is appropriate vs writing lock-free code, how publication/inlining defeats it, and the interaction with the Java Memory Model and other escape-analysis optimizations.

## Why locks exist A **`synchronized` block** (or synchronized method) acquires a **monitor** — a per-object lock — so that only one thread at a time can execute the protected region against that object. Its entire purpose is to coordinate **multiple threads** sharing mutable state and to enforce memory-visibility guarantees between them. Acquiring and releasing a monitor has a cost: in the uncontended case it is cheap but not free (atomic operations and memory barriers); under contention it is much more expensive. ## The insight behind lock elision If a lock object can only ever be touched by **one** thread, then there is never another thread to coordinate with — the lock is guarding against contention that cannot happen. In that situation the `synchronized` is doing no useful work. **Lock elision** (also called **synchronization elision**) is the JIT optimization that detects this and removes the lock: it drops the monitor enter/exit instructions and keeps only the code inside the block. ## How escape analysis enables it The key question is: *can any other thread ever observe this lock object?* That is exactly what **escape analysis** answers. If the lock object is proven **NoEscape** — created in this method, never returned, never stored in a shared/static/instance field, never thrown, never handed to another thread — then by construction no other thread can reach it, so no other thread can ever acquire its monitor. The lock is therefore safe to elide. ## The canonical example ```java String build(String a, String b) { StringBuffer sb = new StringBuffer(); // StringBuffer methods are synchronized sb.append(a); sb.append(b); return sb.toString(); } ``` Every `append` and `toString` on `StringBuffer` is `synchronized`. But `sb` is a NoEscape local: no other thread can see it. The JIT elides all of those locks, so the synchronized `StringBuffer` runs as fast as the unsynchronized `StringBuilder` would here. (You should still prefer `StringBuilder` in source; the point is the JIT can rescue the synchronized case.) Similar things happen with a locally-confined `Vector` or a `Collections.synchronizedList` that never escapes. ## When it is safe — and when it is not **Safe to elide** when the lock object is NoEscape: single-thread visibility is guaranteed, so removing the lock changes no observable behavior. **Not safe (and not applied)** the moment the object could be shared: - it is returned, stored in a static or instance field, or otherwise published; - it is passed to a method the compiler cannot inline/analyze, so it must assume the object may escape; - it is reachable by another thread. In all these cases the compiler conservatively keeps the lock, because eliding a lock that a second thread might actually use would be a correctness bug (lost mutual exclusion). ## Relationship to other escape-analysis optimizations Lock elision is a sibling of scalar replacement and stack allocation — all three are unlocked by a NoEscape proof. They are independent: an object might be lock-elided but not scalar-replaced, or vice versa, depending on how it is used. Lock elision is governed by `-XX:+EliminateLocks` (default on), itself gated by `-XX:+DoEscapeAnalysis`. ## Don't confuse it with lock coarsening or biased locking - **Lock coarsening** merges several adjacent lock/unlock pairs on the same object into one larger critical section to reduce repeated enter/exit overhead — it does *not* require non-escape. - **Biased locking** (now removed/deprecated in modern JDKs) was a different runtime trick to make uncontended locks cheap by biasing a monitor toward one thread. Lock elision specifically *removes* the lock entirely based on proven single-thread visibility, which only escape analysis can establish.

  • Why is it safe to remove the synchronization on a local StringBuffer?
    Escape analysis proves the StringBuffer is NoEscape, so no other thread can ever see it or contend for its monitor; the lock therefore protects nothing and removing it changes no observable behavior.
  • How does lock elision differ from lock coarsening?
    Elision removes a lock entirely because the object can't be shared (requires NoEscape). Coarsening keeps the lock but merges several adjacent lock/unlock pairs on the same object into one larger critical section to cut repeated enter/exit overhead, and doesn't require non-escape.

saying these in an interview costs you the question

  • Confusing lock elision with lock coarsening (merging adjacent critical sections)
  • Confusing it with biased locking (a removed cheap-uncontended-lock mechanism)
  • Claiming the JIT drops locks on shared objects — it requires proven single-thread visibility
  • Saying it makes synchronized always free — only for non-escaping lock objects in JIT'd code

context