Laboratory · Memory
Where does a running Java program keep everything?
Five stations, from the whole process down to one object: the areas a JVM uses, a program’s stack and heap changing statement by statement, one frame opened up, the bytes of an object, and the heap and native memory as each collector organizes them.
Station 1 of 5 · Memory observatory
Which areas of memory does a running JVM use, and which ones does the specification define?
Advanced
Investigations
Questions that come up in real systems, each answered with output from a real JVM: references and reachability, stacks and threads, object layout, and how objects move between generations.
References and objects
A reference is not an addressWhat does a variable of a class type actually hold?
A reference: something the JVM can use to find the object. It is not the object, and it need not be its address.
JOL · Person, default flags (compressed oops and class pointers)Output # VM mode: 64 bits # Compressed references (oops): 3-bit shift # Compressed class pointers: 0-bit shift and 0x32000000 base # Object alignment: 8 bytes Person object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0x01052000 12 4 java.lang.String Person.name null Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes totalVerified with OpenJDK 25.0.4 · Linux x64; JOL 0.17 (java -jar jol-cli.jar internals); header values differ between runs
JOL · Person with -XX:-UseCompressedOopsOutput # Compressed references (oops): disabled # Compressed class pointers: 0-bit shift and 0xD000000 base Person object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0x01052000 12 4 (alignment/padding gap) 16 8 java.lang.String Person.name null Instance size: 24 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes totalVerified with OpenJDK 25.0.4 · Linux x64; JOL 0.17 (java -jar jol-cli.jar internals)
- SpecJVMS §2.7 Representation of Objects(opens in a new tab)
The JVM does not mandate any particular internal structure for objects. · Java SE 25
- SourcemarkWord.hpp(opens in a new tab)
Object header (mark word) bit layout, with and without compact headers. · JDK 25 GA (tag jdk-25-ga)
- Spec
Unreachable is not reclaimedDoes `p = null` free memory?
No. It removes one reference. The object stays until a collection decides to reclaim it.
Identity.java: the same statements, observedJava Person p1 = new Person("Alex"); Person p2 = p1; p2.setName("Yaksh"); System.out.println("p1.getName() = " + p1.getName()); System.out.println("p1 == p2: " + (p1 == p2)); WeakReference<Person> watch = new WeakReference<>(p1); p1 = null; p2 = null; System.out.println("after p1 = null and p2 = null, still in the heap: " + (watch.get() != null)); System.gc(); System.out.println("after System.gc(), weak reference cleared: " + (watch.get() == null));Verified with OpenJDK 25.0.4 · Linux x64
java -cp . IdentityOutput p1.getName() = Yaksh p1 == p2: true after p1 = null and p2 = null, still in the heap: true after System.gc(), weak reference cleared: trueVerified with OpenJDK 25.0.4 · Linux x64 (G1; System.gc() is a request, and the JVM decides what a collection does)
- SpecJLS §12.6 Finalization of Class Instances(opens in a new tab)
Reachability terminology and the (deprecated) finalization mechanism. · Java SE 25
- APIjava.lang.ref package(opens in a new tab)
Strong, soft, weak and phantom reachability. · Java SE 25
- Spec
Stacks and frames
StackOverflowErrorHow deep can a thread’s stack go?
It depends on the stack size and on how big each frame is. With the default size, about 22,000 calls of a tiny method in our runs.
Overflow.java: recursion without a base caseJava public class Overflow { static int depth = 0; static void down() { depth++; down(); } public static void main(String[] args) { try { down(); } catch (StackOverflowError e) { System.out.println("StackOverflowError after " + depth + " frames of down()"); } } }Verified with OpenJDK 25.0.4 · Linux x64
Three runs: twice with the default stack, once with -Xss256kOutput $ java -cp . Overflow StackOverflowError after 22182 frames of down() $ java -cp . Overflow StackOverflowError after 22071 frames of down() $ java -Xss256k -cp . Overflow StackOverflowError after 2470 frames of down()Verified with OpenJDK 25.0.4 · Linux x64; ThreadStackSize = 1024 KB by default here; the depth differs between runs as methods are JIT-compiled
The same recursion, uncaught (first lines)Output Exception in thread "main" java.lang.StackOverflowError at Uncaught.down(Uncaught.java:3) at Uncaught.down(Uncaught.java:3) at Uncaught.down(Uncaught.java:3) …Verified with OpenJDK 25.0.4 · Linux x64; 1,025 lines in all, because MaxJavaStackTraceDepth is 1024
- SpecJVMS §2.5.2 Java Virtual Machine Stacks(opens in a new tab)
Per-thread stacks of frames; StackOverflowError and OutOfMemoryError conditions. · Java SE 25
- SourcestackOverflow.hpp(opens in a new tab)
Stack guard zones: reserved, yellow, red and shadow zones. · JDK 25 GA (tag jdk-25-ga)
- ToolThe java command(opens in a new tab)
Launcher syntax, -Xss, -Xmx, -Xlog and the HotSpot -XX options. · JDK 25
- Spec
Threads share the heap, not their stacksWhat does a new thread get of its own?
Its own stack (and pc register). Everything in the heap is shared.
java -cp . TwoThreadsOutput worker renamed the shared Person worker stack: Renamer.run(TwoThreads.java:23) worker stack: java.base/java.lang.Thread.run(Thread.java:1474) main sees: Yaksh main stack: TwoThreads.main(TwoThreads.java:8)Verified with OpenJDK 25.0.4 · Linux x64; StackWalker skips JDK frames it treats as hidden, such as Thread.runWith
jcmd <pid> Thread.print (two of the threads)Output "main" #3 [4764] prio=5 os_prio=0 cpu=35.56ms elapsed=3.49s tid=0x00007f306001a5f0 nid=4764 waiting on condition [0x00007f30657fe000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleepNanos0(java.base@25.0.4.1/Native Method) at java.lang.Thread.sleepNanos(java.base@25.0.4.1/Thread.java:509) at java.lang.Thread.sleep(java.base@25.0.4.1/Thread.java:540) at Workload.main(Workload.java:18) "helper" #23 [4784] daemon prio=5 os_prio=0 cpu=0.38ms elapsed=3.45s tid=0x00007f30601764c0 nid=4784 waiting on condition [0x00007f30392fd000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleepNanos0(java.base@25.0.4.1/Native Method) …Verified with OpenJDK 25.0.4 · Linux x64; ids and addresses differ between runs
- SpecJVMS §2.5.2 Java Virtual Machine Stacks(opens in a new tab)
Per-thread stacks of frames; StackOverflowError and OutOfMemoryError conditions. · Java SE 25
- APIjava.lang.StackWalker(opens in a new tab)
Walks the frames of the current thread's stack. · Java SE 25
- ToolThe jcmd command(opens in a new tab)
Diagnostic commands for a running JVM: VM.classloaders, GC.heap_info, VM.native_memory, Thread.print, GC.heap_dump. · JDK 25
- Spec
Object layout
Inside the mark wordWhat does HotSpot store in the 8-byte mark word?
The object’s identity hash code, its age (how many collections it survived) and lock bits.
src/hotspot/share/oops/markWord.hpp (the layout comment)Output // 64 bits: // -------- // unused:22 hash:31 -->| unused_gap:4 age:4 self-fwd:1 lock:2 (normal object) // // 64 bits (with compact headers): // ------------------------------- // klass:22 hash:31 -->| unused_gap:4 age:4 self-fwd:1 lock:2 (normal object)Verified with OpenJDK source at the jdk-25-ga tag, the version OpenJDK 25.0.4 is built from
JOL · Person with -XX:+UseCompactObjectHeadersOutput Person object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x010c000000000001 (Lilliput) 8 4 java.lang.String Person.name null 12 4 (object alignment gap) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalVerified with OpenJDK 25.0.4 · Linux x64; JOL 0.17 (java -jar jol-cli.jar internals); JOL calls compact headers by their project name, Lilliput
- SourcemarkWord.hpp(opens in a new tab)
Object header (mark word) bit layout, with and without compact headers. · JDK 25 GA (tag jdk-25-ga)
- JEPJEP 519: Compact Object Headers(opens in a new tab)
Compact object headers became a product feature (off by default). · JDK 25
- Source
Compressed class pointersWhy can the class pointer be 4 bytes?
Because class descriptions live in a reserved region, a short number is enough to find one.
java -XX:-UseCompressedClassPointers …Output OpenJDK 64-Bit Server VM warning: Option UseCompressedClassPointers was deprecated in version 25.0 and will likely be removed in a future release. [0.005s][warning][cds] Unable to use shared archive file. [ ] The saved state of UseCompressedOops and UseCompressedClassPointers is different from runtime, CDS will be disabled.Verified with OpenJDK 25.0.4 · Linux x64
- SourcecompressedKlass.hpp(opens in a new tab)
Compressed class pointers: 32-bit references to classes in the class space. · JDK 25 GA (tag jdk-25-ga)
- JEPJEP 519: Compact Object Headers(opens in a new tab)
Compact object headers became a product feature (off by default). · JDK 25
- Source
Heap and collectors
Watching objects move between generationsHow do survivors reach the old generation?
New objects start young. Each collection copies the few survivors; after a few, they are promoted to the old area.
The same program with -XX:+UseSerialGCOutput [0.003s][info][gc] Using Serial [0.022s][info][gc,heap] GC(0) DefNew: 8704K(9792K)->189K(9792K) Eden: 8704K(8704K)->0K(8704K) From: 0K(1088K)->189K(1088K) [0.022s][info][gc,heap] GC(0) Tenured: 1156K(21888K)->1156K(21888K) [0.022s][info][gc ] GC(0) Pause Young (Allocation Failure) 9M->1M(30M) 0.457ms … [0.029s][info][gc,heap] GC(6) DefNew: 9330K(9792K)->524K(9792K) Eden: 8704K(8704K)->0K(8704K) From: 626K(1088K)->524K(1088K) [0.029s][info][gc,heap] GC(6) Tenured: 1156K(21888K)->1345K(21888K) [0.029s][info][gc ] GC(6) Pause Young (Allocation Failure) 10M->1M(30M) 0.495msVerified with OpenJDK 25.0.4 · Linux x64; in GC(6) survivors are promoted: Tenured grows from 1156K to 1345K
java -Xms32m -Xmx32m -Xlog:gc,gc+heap=info -cp . Churn (G1)Output [0.004s][info][gc] Using G1 [0.026s][info][gc,heap] GC(0) Eden regions: 17->0(13) [0.026s][info][gc,heap] GC(0) Survivor regions: 0->1(3) [0.026s][info][gc,heap] GC(0) Old regions: 2->2 [0.026s][info][gc,heap] GC(0) Humongous regions: 0->0 [0.026s][info][gc ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 18M->1M(32M) 0.932ms … [0.037s][info][gc,heap] GC(5) Eden regions: 18->0(17) [0.037s][info][gc,heap] GC(5) Survivor regions: 1->2(3) [0.037s][info][gc,heap] GC(5) Old regions: 2->2 [0.037s][info][gc ] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 20M->2M(32M) 0.690ms kept 2000 arraysVerified with OpenJDK 25.0.4 · Linux x64; Churn allocates 200,000 long[64] arrays and keeps every hundredth
- GuideHotSpot Virtual Machine Garbage Collection Tuning Guide(opens in a new tab)
Generations, ergonomics, and the G1, Parallel, Serial, Z and other collectors. · JDK 25
- JEPJEP 158: Unified JVM Logging(opens in a new tab)
The -Xlog framework used for class, GC and other logs. · JDK 9
- Guide
Knowledge check
Check your understanding of memory
5 questions. Answers are explained, right or wrong.
Read me
Accuracy and sources
- SpecJVMS §2.5 Run-Time Data Areas(opens in a new tab)
Defines the data areas used during program execution and which are per thread. · Java SE 25
- SpecJVMS §2.6 Frames(opens in a new tab)
A frame is created per method invocation and destroyed when it completes. · Java SE 25
- SpecJVMS §2.7 Representation of Objects(opens in a new tab)
The JVM does not mandate any particular internal structure for objects. · Java SE 25
- SpecJLS §12.6 Finalization of Class Instances(opens in a new tab)
Reachability terminology and the (deprecated) finalization mechanism. · Java SE 25
- GuideHotSpot Virtual Machine Garbage Collection Tuning Guide(opens in a new tab)
Generations, ergonomics, and the G1, Parallel, Serial, Z and other collectors. · JDK 25
- JEPJEP 122: Remove the Permanent Generation(opens in a new tab)
Class metadata moved from PermGen (Java heap) to Metaspace (native memory). · JDK 8
- JEPJEP 519: Compact Object Headers(opens in a new tab)
Compact object headers became a product feature (off by default). · JDK 25
- SourcemarkWord.hpp(opens in a new tab)
Object header (mark word) bit layout, with and without compact headers. · JDK 25 GA (tag jdk-25-ga)
- SourcememTracker.hpp(opens in a new tab)
Native Memory Tracking: HotSpot's accounting of its own native memory, by category. · JDK 25 GA (tag jdk-25-ga)
- ToolJOL (Java Object Layout)(opens in a new tab)
Prints the field offsets, headers and padding of objects in the running JVM. · 0.17