Java Virtual Machine | +----------------+----------------+ | | | Heap Stacks Metaspace | | | Objects Method calls Class metadata Arrays Local variables Runtime metadata Instance data References Method information | Garbage CollectorJava Memory Management: Heap, Stack, Metaspace, and Garbage Collection
Direct Answer & Core Concept
Java memory management is one of the reasons Java applications can run reliably without requiring developers to manually allocate and free memory. The Java Virtual Machine (JVM) manages memory for objects, method calls, class metadata, and other runtime data.
Understanding the Java heap, stack, metaspace, and garbage collection helps developers write more efficient applications and troubleshoot problems such as OutOfMemoryError, excessive memory usage, and slow garbage collection.
In this tutorial, you will learn how Java memory is organized, where objects and local variables are stored, how garbage collection works, and how to avoid common memory-related problems.
Detailed Explanation & Mechanics
What Is Java Memory Management?
Java memory management is the process used by the JVM to allocate, organize, use, and reclaim memory while a Java program is running.
Unlike languages where developers explicitly call functions such as malloc() and free(), Java automatically manages most application memory through the JVM and its garbage collector.
A simple Java program may use several runtime memory areas:
Heap
Java stacks
Metaspace
Program Counter (PC) registers
Native method stacks
The heap and stack are the two areas developers encounter most often, while metaspace is especially important when working with classes, class loaders, frameworks, and dynamic class generation.
Java Memory Structure
A simplified view of JVM memory looks like this:
Each memory area has a different purpose. The heap mainly stores dynamically allocated objects, stacks hold execution-related data for individual threads, and metaspace stores class metadata.
1. Java Heap Memory
The heap is the main runtime memory area where Java objects and arrays are allocated.
Whenever you create an object using new, the object is normally allocated on the heap.
class User {
String name;
User(String name) {
this.name = name;
}
}public class Main {
public static void main(String[] args) {
User user = new User("Tanmoy");
System.out.println(user.name);
}
}Here, the User object is created on the heap.
The variable user is a reference used to access that object. The exact physical representation of references and objects is JVM implementation-dependent, but conceptually it is useful to think of the object as living on the heap and the local reference as belonging to the executing method's stack frame.
What Is Stored in the Heap?
The heap primarily contains dynamically allocated objects and arrays.
For example:
int[] numbers = new int[5];
String name = new String("Java");
User user = new User("Alex");The arrays and objects are allocated in heap memory.
The heap can also contain the object graphs formed when objects refer to other objects:
User | +---- Address | +---- Orders | +---- Order +---- OrderThe garbage collector examines object reachability to determine which heap objects can eventually be reclaimed.
Heap Is Shared Between Threads
The Java heap is generally shared by all threads belonging to the JVM.
For example:
class Counter {
int value = 0;
}If multiple threads access the same Counter object, they can access the same heap object.
Thread 1 | | Counter | Heap | Thread 2Because shared heap data can be accessed concurrently, applications may need synchronization or other concurrency mechanisms when multiple threads modify the same mutable objects.
Heap Generations
Many Java garbage collectors organize heap memory into regions or generations, although the exact organization depends on the collector.
A traditional generational model looks like:
Heap | +----------------------+ | Young Generation | | | | Eden | | Survivor | | Survivor | +----------------------+ | +----------------------+ | Old Generation | +----------------------+New objects are commonly allocated in a young area.
Objects that remain reachable through several collection cycles may eventually be promoted to an older area.
The exact implementation differs between collectors, so developers should avoid assuming that every JVM uses exactly the same heap layout.
Young Generation
The young generation is designed around the observation that many objects become unreachable shortly after they are created.
For example:
for (int i = 0;
i < 100000;
i++) {
String message = "Temporary " + i;
}Many temporary objects created during such operations may become unreachable relatively quickly.
Garbage collection can reclaim unreachable objects so their memory can be reused.
Eden Space
In traditional generational terminology, new objects are initially allocated in the Eden area.
For example:
User user = new User("Alex");The newly created object may initially be allocated in Eden.
When a young-generation collection occurs, objects that are still reachable can be moved or otherwise retained in survivor areas.
Survivor Spaces
Traditional generational collectors commonly use survivor areas to hold objects that survive young-generation collections.
A simplified model is:
Eden | | Collection vSurvivor | | Survives more collections vOld GenerationThe exact behavior depends on the garbage collector being used.
Old Generation
Objects that remain reachable for a longer period may eventually be considered long-lived and promoted to an older region or generation.
Examples of long-lived objects might include:
static User currentUser;
List<User> users = new ArrayList<>();However, simply being stored in a particular kind of variable does not by itself determine an object's generation. Garbage collector policies and object lifetime determine how objects are managed.
2. Java Stack Memory
Each Java thread has its own Java Virtual Machine stack.
The stack is mainly used for method execution. Each method invocation creates a stack frame containing information needed to execute that method.
Consider:
public static void main(String[] args) {
calculate();
}static void calculate() {
int x = 10;
int y = 20;
int result = x + y;
System.out.println(result);
}When calculate() starts, the JVM creates a stack frame for that method.
A simplified representation is:
Thread | +-- main() frame | +-- calculate() frame | +-- x +-- y +-- resultWhen calculate() finishes, its stack frame is removed.
Stack Is Per Thread
Unlike the shared heap, each Java thread has its own JVM stack.
Thread 1 Thread 2 | | Stack 1 Stack 2 | | Method frames Method framesThis separation helps each thread maintain its own method execution state.
However, references stored in different stack frames can point to the same object on the shared heap.
Local Variables and the Stack
Primitive local variables are commonly described as being stored in stack frames.
For example:
public static void test() {
int age = 35;
double salary = 50000.0;
}The values of local variables are associated with the method's stack frame.
However, JVM implementations are free to optimize how values are represented internally. Therefore, "primitive variables always live on the stack" should be treated as a useful conceptual model rather than a strict implementation rule.
References and the Stack
Consider:
User user = new User("Alex");Conceptually:
Stack | | user reference vHeap | | User object | name = "Alex"The local variable user belongs to the method's execution frame, while the User object is allocated on the heap.
The reference itself is not the object.
Stack Example
Consider this method:
static int add(int a, int b) {
int result = a + b;
return result;
}When add() executes, a stack frame is created for the method.
Stack Frame: add()a -> 10b -> 20result -> 30When the method returns, the frame is no longer needed.
StackOverflowError
Stack memory has a limited capacity.
If a program creates too many nested method calls, the stack can become exhausted.
A common example is infinite recursion:
public static void recursiveMethod() {
recursiveMethod();
}public static void main(String[] args) {
recursiveMethod();
}Eventually, the JVM may throw:
java.lang.StackOverflowErrorA common cause is uncontrolled recursion.
3. Java Metaspace
Metaspace is native memory used by the JVM for class metadata.
It replaced the old PermGen (Permanent Generation) approach in Java 8.
Metaspace can contain metadata associated with loaded classes and related runtime structures.
For example:
class Employee {
private int id;
private String name;
}When the JVM loads Employee, it needs memory for information describing that class.
That class-related metadata is associated with metaspace.
Metaspace vs Heap
Heap and metaspace serve different purposes.
| Area | Main Purpose |
|---|---|
| Heap | Objects and arrays |
| Stack | Method execution and stack frames |
| Metaspace | Class metadata |
| Native memory | JVM/native runtime needs |
A simple mental model is:
Heap -> Employee objects -> String objects -> ArraysStack -> Method frames -> Local execution dataMetaspace -> Class metadata -> Runtime class informationPermGen vs Metaspace
Before Java 8, class metadata was associated with the Permanent Generation (PermGen).
Java 8 removed PermGen and introduced Metaspace.
Older Java | +-- PermGenJava 8+ | +-- MetaspaceMetaspace uses native memory rather than being part of the traditional Java heap.
You can configure a maximum metaspace size using:
-XX:MaxMetaspaceSize=256mFor example:
java -XX:MaxMetaspaceSize=256m MyApplication4. Garbage Collection in Java
Garbage Collection (GC) is the JVM mechanism that automatically identifies objects that are no longer reachable and reclaims memory associated with them.
For example:
public static void main(String[] args) {
User user = new User("Alex");
user = null;
}After:
user = null;the previously referenced object may become unreachable, assuming no other references point to it.
It then becomes eligible for garbage collection.
What Does "Eligible for Garbage Collection" Mean?
An object is not necessarily collected immediately when it becomes unreachable.
For example:
User user = new User("Alex");
user = null;The object may now be unreachable.
That means:
Object -> unreachable -> eligible for GCIt does not mean:
Object -> immediately deletedThe JVM decides when and how garbage collection occurs.
Garbage Collection and Reachability
Modern garbage collectors generally determine whether objects are reachable from GC roots.
Examples of GC roots can include:
Active thread stacks
Static references
JNI references
Other JVM-defined root references
A simplified example:
GC Root | vObject A | vObject BBoth objects are reachable.
If no GC root can reach an object:
GC RootObject A Object B | X unreachableThe unreachable object can become eligible for reclamation.
Java Garbage Collection Example
class Employee {
String name;
Employee(String name) {
this.name = name;
}
}public class Main {
public static void main(String[] args) {
Employee employee = new Employee("John");
employee = null;
System.out.println("Employee reference removed");
}
}After assigning null, the original Employee object may become unreachable.
The JVM can reclaim its memory later when the garbage collector determines that collection is appropriate.
Can We Force Garbage Collection?
Java provides:
System.gc();For example:
System.gc();However, this is only a request or hint to the JVM. It does not guarantee that garbage collection will happen immediately.
Therefore, application code should generally not depend on System.gc() for correctness.
Types of Garbage Collection
Java has several garbage collectors designed for different workloads and requirements.
Common collectors include:
Serial GC
Parallel GC
G1 GC
ZGC
Shenandoah
The appropriate collector depends on factors such as heap size, throughput requirements, latency goals, and the Java version.
Serial Garbage Collector
The Serial GC is a relatively simple collector that uses a single thread for garbage collection work.
It can be suitable for small applications and environments where simplicity matters.
Example option:
-XX:+UseSerialGCParallel Garbage Collector
The Parallel GC uses multiple threads for garbage collection and is designed primarily around throughput.
Example:
-XX:+UseParallelGCIt can be useful when maximizing overall application throughput is more important than minimizing every individual pause.
G1 Garbage Collector
G1 (Garbage-First) GC divides the heap into regions and tries to focus collection work on regions containing significant amounts of reclaimable memory.
Example:
-XX:+UseG1GCG1 is a common general-purpose collector for modern Java applications.
Z Garbage Collector
ZGC is designed for very low pause times, including applications with large heaps.
Example:
-XX:+UseZGCZGC performs much of its work concurrently with application execution.
This makes it useful for applications where latency is particularly important.
Shenandoah Garbage Collector
Shenandoah is another low-pause garbage collector designed to perform substantial GC work concurrently with application threads.
The availability and exact behavior of collectors can depend on the Java distribution and version.
Stop-The-World Pauses
Some garbage collection operations may temporarily pause application threads.
This is commonly called a Stop-The-World (STW) pause.
Conceptually:
Application Threads | | Running v[ GC Pause] | | Resume v ApplicationModern collectors perform substantial work concurrently to reduce pause times, but some STW phases can still occur.
Minor, Major, and Full GC Terminology
You may encounter terms such as:
Minor GC
Major GC
Full GC
These terms are widely used in Java discussions, but their exact meaning can vary depending on the garbage collector and JVM implementation.
Therefore, when diagnosing a production application, it is better to look at the actual collector, GC logs, pause events, and memory behavior rather than relying only on generic labels.
Java Memory Lifecycle
A simplified object lifecycle looks like this:
Object Created | v Heap | vObject Remains Reachable | vObject Becomes Unreachable | vEligible for Garbage Collection | vMemory ReclaimedThe garbage collector determines when the memory can actually be reclaimed.
Strong References
The most common Java references are strong references.
User user = new User("Alex");As long as a reachable strong reference leads to the object, the object is normally considered reachable.
If the reference is removed:
user = null;the object may become eligible for garbage collection if no other reachable references exist.
Weak References
Java also provides reference types such as WeakReference.
Example:
WeakReference<User> reference = new WeakReference<>(new User("Alex"));Weak references can be useful in specialized memory-sensitive designs such as certain cache implementations.
They should not be used simply because garbage collection is desired; reference types have specific semantics and use cases.
Memory Leak in Java
Java has automatic garbage collection, but Java applications can still experience memory leaks.
A Java memory leak occurs when objects are no longer needed by the application but remain reachable, preventing the garbage collector from reclaiming them.
For example:
static List<Object> cache = new ArrayList<>();
public static void addData(Object data) {
cache.add(data);
}If the list continuously grows without removing objects, memory usage can keep increasing.
The objects are still reachable through cache, so the garbage collector cannot simply reclaim them.
Common Causes of Java Memory Leaks
Common causes include:
1. Unbounded Collections
List<String> logs = new ArrayList<>();
while (true) {
logs.add("New log entry");
}The list continues growing.
2. Static Collections
static Map<String, Object> cache = new HashMap<>();A static collection can keep objects reachable for the lifetime of the class loader.
3. Unremoved Listeners
Objects registered as listeners but never unregistered can remain reachable longer than intended.
4. ThreadLocal Data
Improper use of ThreadLocal in long-lived threads can retain objects.
5. Large Caches
A cache without a sensible size or eviction policy can consume increasing amounts of heap memory.
OutOfMemoryError
If the JVM cannot allocate required memory, it may throw an OutOfMemoryError.
A common example is:
java.lang.OutOfMemoryError: Java heap spaceThis generally indicates that the JVM could not allocate an object in the Java heap.
Other memory-related errors include:
java.lang.OutOfMemoryError: Metaspaceand potentially other messages depending on the JVM and resource involved.
StackOverflowError vs OutOfMemoryError
These errors have different causes.
| Error | Typical Cause |
|---|---|
StackOverflowError | Excessive stack usage, often recursion |
OutOfMemoryError: Java heap space | Heap cannot satisfy allocation |
OutOfMemoryError: Metaspace | Class metadata/metaspace exhaustion |
| Native-memory-related OOM | JVM/native memory pressure |
Example stack overflow:
static void test() {
test();
}Example heap pressure:
List<byte[]> data = new ArrayList<>();
while (true) {
data.add(new byte[1024 * 1024]);
}Heap Size Configuration
You can configure the initial and maximum heap sizes using JVM options.
For example:
java -Xms512m -Xmx2g MyApplicationHere:
-Xms512msets the initial heap size-Xmx2gsets the maximum heap size
These settings control the Java heap, not all memory used by the JVM process.
Metaspace Configuration
You can configure metaspace using:
-XX:MetaspaceSize=128mand:
-XX:MaxMetaspaceSize=512mExample:
java -XX:MetaspaceSize=128m \ -XX:MaxMetaspaceSize=512m \ MyApplicationThe exact sizing should be based on application behavior rather than arbitrary values.
Monitoring Java Memory
Several tools can help investigate JVM memory usage.
Useful tools include:
jpsjstatjcmdjmapFor example:
jcmd <PID> GC.heap_infoYou can first find the Java process:
jps -lThen inspect its heap information.
Heap Dump
A heap dump is a snapshot of objects and their relationships in the Java heap.
It can help investigate memory leaks and unexpected object retention.
A commonly used command is:
jcmd <PID> GC.heap_dump /tmp/heap.hprofThe resulting .hprof file can be analyzed using heap-analysis tools.
A heap dump can help answer questions such as:
Which objects consume the most memory?
Which objects are retaining other objects?
Why is a collection continuously growing?
Which object types have unusually high counts?
Java Memory Example
Consider this program:
import java.util.ArrayList;
import java.util.List;
public class MemoryDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
printNames(names);
} static void printNames(List<String> names) {
int count = names.size();
System.out.println("Names: " + names);
System.out.println("Count: " + count);
}
}Conceptually:
Heap | +-- ArrayList object | +-- Array backing storage | +-- String objectsStack | +-- main() frame | +-- names reference | +-- printNames() frame | +-- names reference +-- countThe names reference in both methods can refer to the same heap object.
Heap vs Stack vs Metaspace
The differences are important for Java developers.
| Feature | Heap | Stack | Metaspace |
|---|---|---|---|
| Main purpose | Objects and arrays | Method execution | Class metadata |
| Shared? | Generally shared | Per thread | JVM-level/native memory |
| Managed by GC? | Yes | Stack frames disappear with calls | Class unloading can reclaim associated metadata |
| Typical problem | Heap OOM | Stack overflow | Metaspace OOM |
| Example | new User() | Local method frame | Loaded class metadata |
Java Memory Management Best Practices
Good memory management is mostly about controlling object lifetime and avoiding unnecessary retention.
1. Avoid Unbounded Collections
Do not allow lists, maps, or caches to grow forever.
2. Release Unneeded References
For long-lived objects, make sure references to objects that are no longer required do not remain unnecessarily reachable.
3. Use Bounded Caches
Caching can improve performance, but caches should generally have a sensible size or eviction strategy.
4. Avoid Creating Unnecessary Objects
For example, avoid repeatedly creating expensive temporary objects when a simpler design can reuse data safely.
5. Close Resources
Garbage collection is not a replacement for explicit resource management.
Use try-with-resources for resources such as files and streams:
try(var input = new java.io.FileInputStream("data.txt")) {
// Read data
}6. Monitor Production Memory
Track heap usage, GC pauses, allocation rates, and process-level memory rather than waiting for an OutOfMemoryError.
7. Investigate Leaks Instead of Only Increasing -Xmx
Increasing the heap can delay a memory problem but does not necessarily fix the underlying cause.
Common Java Memory Mistakes
Mistake 1: Thinking System.gc() Forces GC
System.gc();This does not guarantee immediate garbage collection.
Mistake 2: Assuming null Immediately Frees Memory
user = null;This only removes that particular reference.
The object may still be reachable through another reference.
Mistake 3: Assuming Java Cannot Have Memory Leaks
Garbage collection prevents many manual-memory errors, but applications can still accidentally retain objects.
Mistake 4: Treating Heap and Stack as the Entire JVM Memory
The JVM uses several memory areas and native resources. Process memory can therefore be significantly larger than the configured Java heap.
Mistake 5: Increasing Heap Size Without Investigation
Changing:
-Xmx2gto:
-Xmx8gmay postpone an issue while increasing memory consumption.
First investigate object retention, allocation behavior, GC activity, and other JVM memory areas.
Simple Java Memory Diagram
For beginners, this diagram provides a useful mental model:
JVM MEMORY | +--------------+--------------+ | | | HEAP STACK METASPACE | | | Objects Method frames Class metadata Arrays Local data Runtime metadata | | | | Garbage One stack Collection per threadA more complete JVM picture also includes areas such as the PC register and native method stack.
Frequently Asked Questions
What is heap memory in Java?
Heap memory is the main runtime area used for dynamically allocated objects and arrays. It is shared among threads and managed by the JVM's garbage collection mechanisms.
What is stack memory in Java?
Each Java thread has its own JVM stack, which contains stack frames used during method execution. These frames hold execution-related information such as local variables and operand data.
What is metaspace in Java?
Metaspace is native memory used for class metadata. It replaced PermGen starting with Java 8.
Does Java automatically free memory?
Java's garbage collector automatically reclaims memory associated with objects that are no longer reachable. Resource management, such as closing files and database connections, still needs appropriate application-level handling.
Can Java have memory leaks?
Yes. A memory leak can happen when an application unintentionally keeps references to objects that it no longer needs.
What causes StackOverflowError?
Excessive stack usage commonly causes StackOverflowError. Infinite or extremely deep recursion is a common example.
What causes Java heap OutOfMemoryError?
It can occur when the JVM cannot allocate an object because the heap cannot provide enough usable memory. Excessive object retention, large allocations, or an undersized heap can contribute.
Is the stack faster than the heap?
Stack operations associated with method execution are generally simple and have different allocation/lifetime characteristics from heap allocation. However, saying "stack is always faster than heap" is an oversimplification because JVM implementations use optimizations and the actual performance depends on the workload.
Does calling System.gc() guarantee garbage collection?
No. System.gc() is only a request to the JVM and should not be used as a correctness mechanism.
What is the difference between heap and metaspace?
The heap primarily stores objects and arrays, while metaspace stores metadata associated with loaded classes.
Graphics and Design Ideas
Graphic 1: JVM Memory Architecture
Create a clean diagram showing:
JVM | +-- Heap | +-- Objects | +-- Arrays | +-- Stack | +-- Method Frames | +-- Local Variables | +-- Metaspace +-- Class MetadataGraphic 2: Java Garbage Collection Lifecycle
Show:
Create Object↓Reachable↓No Longer Reachable↓Eligible for GC↓Garbage Collector↓Memory ReclaimedGraphic 3: Heap Generational Model
Show a simplified:
Young Generation
├── Eden
├── Survivor
└── Survivor
Old GenerationAdd a note that actual heap organization depends on the selected garbage collector.
Graphic 4: Stack vs Heap
Use two columns:
STACK HEAP------ ------Per thread Generally sharedMethod frames ObjectsLocal execution data ArraysShort-lived frames GC-managed objectsThese visuals can make the page easier for beginners to understand and can also improve the readability of the tutorial.
Runnable Code Example: Java Memory Management
Java 17+public class MemoryManagementDemo {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
System.out.println("Available Processors: " + rt.availableProcessors());
System.out.println("Total Heap Memory: " +(rt.totalMemory() /(1024 * 1024)) + " MB");
System.out.println("Max Heap Memory: " +(rt.maxMemory() /(1024 * 1024)) + " MB");
}
}