watching the jvm run
an interactive step-through of jvm internals. classloaders, runtime data areas, and the tiered JIT, one frame and one bytecode at a time.
when you run java Counter, a lot happens before main even returns. classes are loaded in phases by a hierarchy of loaders. every method call carves a frame on the jvm stack. new lands in the heap. a loop that runs hot gets promoted from the bytecode interpreter to C1, and then to C2 with inlining and escape analysis until the call essentially disappears.
this post walks through all of it. there’s a small java program called Counter and 29 numbered steps (0–28) that take the jvm from process launch to exit. three asides (A1/A2/A3, shown with dashed panels) cover behaviours like allocation + minor GC, C2 recompilation, and deoptimization. those would fire under different conditions but don’t actually trigger here. use prev / next or hit play. arrow keys work too. click any panel (classloader, heap, stack, engine…) to pop open an explanation of what it is.
| method | tier | invocations |
|---|
the program
public class Counter {
private int sum;
public void add(int n) {
sum += n;
}
public int get() {
return sum;
}
public static void main(String[] args) {
Counter c = new Counter();
for (int i = 0; i < 10000; i++) {
c.add(i);
}
System.out.println(c.get());
}
}
what’s still missing
not covered yet: class unloading (when a classloader becomes unreachable, all its classes go with it), classloader isolation (web containers and OSGi give each module its own loader), synchronization primitives (monitors, biased/lightweight/heavyweight lock inflation), and reflection’s own caching. some of those deserve their own post.