Skip to content

JVM Runtime Data Areas (Memory Model)

Posted on:July 8, 2026

In the previous blog, we explored how the class loader subsystem works in the JVM, but that’s only half the story: classes are loaded, but we don’t know where they live.

This is where the JVM’s Runtime Data Areas come into play. Understanding run-time data areas is critical to better Java programming. One of the most dreaded errors in Java is OutOfMemoryError, and it is related to JVM memory areas. We should have a better understanding of JVM internals — how its data areas work — so that we have a better grip over these kinds of JVM errors. In this article, we will learn about the types of run-time memory areas in the JVM and how they work.

alt text

Runtime Data Areas are the parts of memory the JVM uses to run your program. Some of these areas are shared by the whole program (shared with all threads) and last as long as the JVM is running (like the heap, where objects live). Others are created for each thread and disappear when that thread finishes (like the stack, which tracks method calls).

Everything below is grounded in the Java Virtual Machine Specification, Java SE 21 Edition — I’ll cite it as JVMS from here on (e.g. JVMS §2.5.4 means “section 2.5.4 of that spec”).

I. Common Memory Areas (shared with all threads)

Method area

Once a class’s bytecode is loaded by a JVM class loader, it’s passed to the JVM for further processing. The JVM creates an internal representation of the class and stores it in the method area. This internal representation is exactly the structured, in-memory representation the Loading step in Part 2 set out to build — Part 2 covered who finds the bytes; the method area is where the result ends up living (JVMS §2.5.4). The following data areas are contained within the internal representation of a class:

alt text

Where do the linking & initialization steps from Part 2 land? The method area is the physical home for most of that work: Loading builds the internal class representation here, Resolution rewrites this class’s runtime constant pool, and Preparation allocates the class’s static fields here with their default values before Initialization overwrites them with their real ones. (Spec-wise those static fields are method-area class data; HotSpot since Java 8 actually keeps their values in the class’s java.lang.Class mirror on the heap — a good reminder that the spec defines what exists and leaves where to the implementation.)

Heap

Everything above is what the JVMS guarantees about the heap — deliberately abstract. How the heap is physically organized (generational layout, PermGen → Metaspace, GC algorithms like G1/ZGC) is a HotSpot implementation detail the spec doesn’t mandate, and it’s a deep-dive in its own right. We’ll cover it next in Heap Structure & Garbage Collection.

Connecting back to Part 2: every trigger that actively uses a class — new Config(), reflective instantiation, the Spring beans and CGLIB proxies from the last post — produces an object, and every one of those objects is allocated here on the heap. The division of labour is clean: class metadata goes to the method area, class instances go to the heap.

II. Exclusive Memory Areas (managed per-thread)

JVM Stack

alt text

A JVM throws a StackOverflowError when a thread needs a stack area larger than is permitted or than the available memory. If a JVM stack is dynamically allocated, a JVM may throw an OutOfMemoryError if insufficient memory is available to meet a stack size increase request. It may also throw an OutOfMemoryError if insufficient memory is available during initial stack allocation.

Stack Frame

A new stack frame is allocated and pushed into a JVM stack every time a method is invoked. A frame is popped from the stack and destroyed upon the completion of method invocation irrespective of whether the completion is normal or abrupt (uncaught exception). Each frame has its own operand stack, an array of local variables, and a reference to the runtime constant pool.

Connecting back to Part 2: this per-thread execution machinery is where the JVM’s laziness finally pays off. Loading, Resolution, and Initialization were all described as happening on first active use — and “active use” concretely means a frame executing a bytecode. When some frame runs new Config(), calls a static method, or reads a static field, that instruction is the trigger that pulls the class through the load → link → initialize pipeline (and runs its static {} block) exactly when it’s needed — never before.

PC Register

In general computer architecture terms, the program counter (PC) register keeps track of the instruction executing at any moment. It is like a pointer to the current instruction in the sequence of instructions in a program. Due to thread switching, the CPU needs to remember the location of the next instruction of the original thread, so each thread needs to have its own PC. The same holds in JVM terms. Because Java supports multithreading, a PC register is created every time a new thread is created. The PC keeps a pointer to the current instruction being executed in its thread. If the currently executing method is native, then the value of the PC register is undefined.

Native Method Stack

Native method stacks are similar to JVM stacks, except that JVM stacks serve Java methods while native method stacks serve native methods. In the HotSpot virtual machine implementation, the native method stack and the JVM stack are combined. As with the JVM stack, a StackOverflowError or an OutOfMemoryError can be thrown.

III. The difference between the stack and the heap

So we know what the stack and heap are in sections above, but what exactly is the difference between the Stack and the Heap, why does the JVM need both instead of just collapsing them into one place?

Lifetime : LIFO vs “live as long as something points at it”

Because in the stack, stack frames follow a strict Last-In, First-Out—LIFO—discipline, so all data in the stack is tied directly to method invocation, they come alive when a method is invoked, and are removed when that invocation completes. No garbage collector is needed to search for individual local variables.

But we also have data which can live outside of a method - you can return it from a method, assign it to a field, hand it to another thread or a static object (you declare a static Map or List in a constants file). This is where the heap comes in. Its lifetime is governed not by who created it but by who still references it. That’s exactly why the heap “must provide a garbage collection mechanism” and the stack does not. There is no simple LIFO rule that tells the JVM exactly when an object is no longer needed, the heap needs a Garbage Collector that starts from a set of roots (the live stack frames, static fields, etc.) and marks every object it can reach by following references. Anything it can’t reach — even if other dead objects still point at it — is garbage.

The deeper idea is this:

If a piece of execution state has a lifetime strictly tied to a scope or call structure, it can often be reclaimed through simple structural rules.

If data can outlive the scope that created it, the runtime needs another ownership or lifetime-management mechanism.

That idea appears again and again across languages and runtimes.

Related Posts