Skip to content

Instantly share code, notes, and snippets.

@lrytz
Created April 23, 2026 15:18
Show Gist options
  • Select an option

  • Save lrytz/c210446b9c49a17a2c93606dd4fafb6b to your computer and use it in GitHub Desktop.

Select an option

Save lrytz/c210446b9c49a17a2c93606dd4fafb6b to your computer and use it in GitHub Desktop.
Nested object locking
// Minimal reproducer for the unbounded native-memory growth exposed by
// scala/scala3#25777. Each iteration holds 100 `synchronized` blocks nested on
// distinct objects, then drops them.
//
// * On JDK <= 22 (`-XX:LockingMode=1` default, legacy stack locking): each
// lock's bookkeeping lives in a Java stack slot. Nothing is allocated in
// native memory; the loop runs in steady state.
//
// * On JDK 23+ (`-XX:LockingMode=2` default, new lightweight locking): each
// thread has a fixed 8-slot LockStack. Taking a 9th or deeper lock allocates
// a heavyweight `ObjectMonitor` in native memory. These are eligible for
// async deflation after release, but this loop produces new monitors faster
// than the deflater can reclaim, so the "Object Monitors" category of
// Native Memory Tracking grows without bound.
//
// Compile & run:
// javac LockNesting.java
// java -XX:NativeMemoryTracking=summary LockNesting &
// PID=$!
// # watch the "Object Monitors" line grow on JDK 23+, stay at 0 on <= 22
// while true; do jcmd $PID VM.native_memory summary | grep -E 'Object Monitors'; sleep 2; done
//
// Or see inflation messages directly:
// java -Xlog:monitorinflation=debug LockNesting | head -20
public class LockNesting {
public static void main(String[] args) {
while (true) {
Object[] objs = new Object[100];
for (int i = 0; i < objs.length; i++) objs[i] = new Object();
nested(objs, 0);
}
}
static void nested(Object[] objs, int i) {
if (i == objs.length) return;
synchronized (objs[i]) {
nested(objs, i + 1);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment