Skip to content

Station 1 of 5 · Memory observatory

Which areas of memory does a running JVM use, and which ones does the specification define?

Make a prediction

Make a prediction

Where does HotSpot (JDK 25) keep the description of a class, its methods and its constant pool?

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 total
    

    Verified 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 total
    

    Verified with OpenJDK 25.0.4 · Linux x64; JOL 0.17 (java -jar jol-cli.jar internals)

  • 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: true
    

    Verified with OpenJDK 25.0.4 · Linux x64 (G1; System.gc() is a request, and the JVM decides what a collection does)

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

  • 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

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 total
    

    Verified 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

  • 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

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.495ms
    

    Verified 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 arrays
    

    Verified with OpenJDK 25.0.4 · Linux x64; Churn allocates 200,000 long[64] arrays and keeps every hundredth

Knowledge check

Check your understanding of memory

5 questions. Answers are explained, right or wrong.

  1. Question 1

    Question 1 · Memory state

    After Person p2 = p1;, how many Person objects exist?

  2. Question 2

    Question 2 · Concept

    Which areas does each thread have to itself?

  3. Question 3

    Question 3 · Concept

    Where are a class’s static fields in HotSpot JDK 25?

  4. Question 4

    Question 4 · Concept

    What does OutOfMemoryError: Metaspace most often point to?

  5. Question 5

    Question 5 · Concept

    How big is a Person object (one reference field) with compact object headers on JDK 25?

Read me

Accuracy and sources