JVM Memory Areas

Naveen AnamaneniUpdated · 5 min read

The JVM doesn't use one big block of memory; it divides memory into several areas, each with its own job. Your objects live in the heap. Each thread has a stack. Class information lives in metaspace. Compiled machine code lives in the code cache. Knowing these areas helps you size the JVM correctly and understand messages such as OutOfMemoryError: Metaspace. In this lesson, you will learn each memory area and how to look at it from a running program.

The Memory Areas

The heap holds objects; stacks, metaspace and the code cache live outside it
AreaHoldsShared?Size set byError when full
HeapAll objects and arrays (young and old generations)Yes-Xms, -XmxOutOfMemoryError: Java heap space
Thread stacksFrames, local variables, operand stacksNo, one per thread-XssStackOverflowError
MetaspaceClass metadata: fields, methods, bytecode, constant poolsYes-XX:MaxMetaspaceSize (unlimited by default)OutOfMemoryError: Metaspace
Code cacheMachine code produced by the JIT compilerYes-XX:ReservedCodeCacheSizeJIT stops compiling (a warning)
Direct memoryByteBuffer.allocateDirect buffersYes-XX:MaxDirectMemorySizeOutOfMemoryError: Direct buffer memory
Program counterThe current instruction of each threadNoFixedNone

Before Java 8, class metadata lived in an area of the heap called PermGen, which caused frequent "PermGen space" errors. Metaspace replaced it and lives in native memory.

Example: Listing the Memory Pools

Let's see an example that asks the running JVM for its memory pools.

MemoryPools.java▶ Try it
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
 
public class MemoryPools {
    public static void main(String[] args) {
        for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
            String type =
                pool.getType() == MemoryType.HEAP ? "heap    " : "non-heap";
            System.out.println(type + "  " + pool.getName());
        }
    }
}
Output
non-heap  CodeHeap 'non-nmethods'
non-heap  Metaspace
non-heap  CodeHeap 'profiled nmethods'
non-heap  Compressed Class Space
heap      G1 Eden Space
heap      G1 Old Gen
heap      G1 Survivor Space
non-heap  CodeHeap 'non-profiled nmethods'

Explanation: The heap appears as three pools of the default G1 garbage collector: Eden and Survivor (the young generation) and Old Gen. Outside the heap are Metaspace, the Compressed Class Space (part of metaspace for class pointers), and three parts of the code cache ("CodeHeap"), which hold JIT-compiled code at different stages. With another collector, the heap pool names change.

Example: Heap Usage Before and After Allocating

HeapUsage.java▶ Try it
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
 
public class HeapUsage {
    public static void main(String[] args) {
        MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
        System.gc();
        long before = memory.getHeapMemoryUsage().getUsed();
 
        int[][] blocks = new int[50][];
        for (int i = 0; i < blocks.length; i++) {
            blocks[i] = new int[250_000];
        }
 
        System.gc();
        long after = memory.getHeapMemoryUsage().getUsed();
        long grewMb = (after - before) / (1024 * 1024);
        System.out.println("Heap grew by at least 40 MB? " + (grewMb >= 40));
        System.out.println("Non-heap is in use too? "
            + (memory.getNonHeapMemoryUsage().getUsed() > 0));
        System.out.println(blocks.length + " blocks kept alive");
    }
}
Output
Heap grew by at least 40 MB? true
Non-heap is in use too? true
50 blocks kept alive

Explanation: Fifty arrays of 250,000 ints are about 50 MB, all on the heap. The non-heap areas (metaspace, code cache) are in use too, holding the program's classes and compiled code. System.gc() is called before both measurements so that leftover garbage doesn't hide the growth; without it, a collection that happens in between can make the heap look smaller than it is. The exact numbers change from run to run, so the program checks a range.

Common Mistakes

Thinking -Xmx caps the whole process. Many areas live outside the heap.

Looking for PermGen in modern Java. It was removed in Java 8.

Ignoring thread stacks. Thousands of platform threads can use gigabytes of stack memory.

Interview Corner

What are the memory areas of the JVM?

The heap stores all objects and arrays and is divided by the garbage collector into young (eden and survivor) and old generations; it's shared by all threads and sized with -Xms/-Xmx. Each thread has a stack of frames for method calls and a program counter. Metaspace, in native memory since Java 8 (replacing PermGen), stores class metadata. The code cache stores JIT-compiled native code. There's also other native memory, such as direct byte buffers and GC data structures.

Practice

  1. Easy: Run MemoryPools with -XX:+UseSerialGC and with -XX:+UseZGC, and compare the heap pool names.
  2. Harder: Load the same class with ten new URLClassLoaders and watch metaspace usage grow with MemoryPoolMXBean.

Quick check: JVM memory areas

4 questions
1Where does class metadata live in modern Java?
2Which area holds JIT-compiled machine code?
3Does -Xmx limit metaspace?
4Which areas are one per thread?

Key Takeaways

Was this tutorial helpful?

Lesson 434 of 482