Complete Guide to JVM Memory Management & Garbage CollectionΒΆ
Memory management is one of Java's core strengths. Instead of requiring developers to manually allocate and free memory, the Java Virtual Machine (JVM) handles memory lifecycle operations automatically in following way: - - Allocates memory for objects - Tracks object usage - Reclaims unused memory automatically - Prevents most memory-related programming errors
Problems with manual memory management:ΒΆ
- Memory leaks
- Dangling pointers
- Double free errors
- Program crashes
Java solves this automatically using Garbage Collection.
Benefits:
- Automatic cleanup
- Better stability
- Reduced memory bugs
- Improved developer productivity
-
Common Causes of Memory Leaks
- Static collections
- Unclosed database connections
- Unclosed streams
- Event listeners not removed
- Cached objects never cleared
- Holding references unnecessarily
Table of ContentsΒΆ
- JVM Memory Architecture Setup
- Object Allocation & Reachability Lifecycle
- The Garbage Collection Execution Flow
- Core GC Algorithms
- JVM Garbage Collector Implementations
- When Does GC Run?
- Manual GC Requests & Deprecated Features
- GC Logs, Flags, & Performance Tuning
- Best Practices for Memory Optimization
- Useful Monitoring Tools
1. JVM Memory Architecture SetupΒΆ
When a Java application boots, the Class Loader Subsystem loads compiled .class bytecode files into memory. The JVM organizes its memory into distinct Runtime Data Areas to execute bytecode efficiently.
JVM Architecture OverviewΒΆ
Java Source Code
β
javac Compiler
β
Bytecode (.class)
β
ββββββββββββββββββββββββββββ
β Class Loader β
ββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββ
β Runtime Data Areas β
ββββββββββββββββββββββββββββ
β β’ Heap β
β β’ Stack β
β β’ Metaspace β
β β’ PC Register β
β β’ Native Method Stack β
ββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββ
β Execution Engine β
β (Interpreter + JIT) β
ββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββ
β Garbage Collector β
ββββββββββββββββββββββββββββ
Main JVM ComponentsΒΆ
1. Class LoaderΒΆ
- Loads
.classfiles into JVM memory.- Instead of loading all classes at once, the JVM loads a class only when it is needed (lazy loading), which improves startup time and memory efficiency.
- Verifies bytecode for security.
- Links and initializes classes before execution.
2. Runtime Data AreasΒΆ
Memory areas used while the program is running:
- Heap
- Stores objects, arrays and instance variables.
- Shared among all threads.
- Managed by the Garbage Collector.
-
- Largest memory area
-
Stack
- Stores method calls, local variables, and references.
- Each thread has its own stack.
-
memory is release automatically when execution /thread finishesh
-
Metaspace (java 8 +)
- Stores class metadata, method information and Runtime constant pool.
- Replaced PermGen from Java 8 onward.
-
Uses native memory(outside head , directly form RAM).
-
PC (Program Counter) Register
- Stores the address of the currently executing JVM instruction.
-
Each thread has its own PC register.
-
Native Method Stack
- Supports execution of native methods written in languages like C or C++.
3. Execution EngineΒΆ
Responsible for executing bytecode.
- Interpreter
- Executes bytecode line by line.
-
Fast startup but slower execution.
-
JIT (Just-In-Time) Compiler
- Compiles frequently executed bytecode into native machine code.
- Improves application performance.
4. Garbage Collector (GC)ΒΆ
- Automatically removes unused objects from the heap.
- Frees memory and prevents memory leaks.
- Runs automatically without programmer intervention.
5. Java Native Interface (JNI)ΒΆ
- Enables Java applications to interact with native code written in languages such as C and C++.
- Allows Java to use platform-specific libraries and system APIs.
5. Heap Memory in DetailΒΆ
Heap is divided into generations because most objects live for a very short time.
Heap
β
βββ Young Generation
β βββ Eden
β βββ Survivor S0
β βββ Survivor S1
β
βββ Old Generation
Young GenerationΒΆ
Every new object is created here.
Contains:
- Eden Space
- Survivor Space 0
- Survivor Space 1
Most objects die here.
Collected frequently.
Uses Minor GC.
Old GenerationΒΆ
Objects surviving several Minor GCs are promoted here.
Contains long-lived objects.
Collected less frequently.
Uses Major/Full GC.
Object LifecycleΒΆ
new Object()
β
βΌ
Young Generation (Eden)
β
Minor GC
β
βΌ
Survivor Space
β
Multiple GC cycles
β
βΌ
Old Generation
β
Major/Full GC
Runtime Data Areas OverviewΒΆ
| Memory Area | Scope | Purpose & Characteristics |
|---|---|---|
| Heap Memory | Shared across all threads | Stores all instantiated objects and arrays. Managed directly by the Garbage Collector. |
| Stack Memory | Thread-private | Stores thread frames, method calls, primitive local variables, and object references. Automatically freed when method execution finishes. |
| Metaspace (Java 8+) | Shared across all threads | Located in off-heap native memory (replacing PermGen). Stores class structures, method metadata, and runtime constant pools. |
| PC Registers | Thread-private | Contains the address of the current JVM instruction being executed. |
| Native Method Stack | Thread-private | Manages native C/C++ method invocations executed via JNI. |
2. Object Allocation & Reachability LifecycleΒΆ
Phase 1: Object CreationΒΆ
-
When you run
new MyObject(), space is allocated inside the Young Generation (specifically the Eden space) of the Heap. -
A reference to that object is placed on the current thread's Stack.
Phase 2: Becoming Eligible for Garbage CollectionΒΆ
An object becomes eligible for Garbage Collection as soon as it becomes unreachable from any active reference root. Common triggers include:
-
Nullifying references:
obj = null; -
Reassigning references:
obj = new OtherObject(); -
Method scope expiration: Local variables on the stack die when the method completes.
-
Collection removal: Removing an element from a List or Map (
list.remove(obj)). -
Class loader unloading: Custom class loader is reclaimed.
Phase 3: Reachability Analysis via GC RootsΒΆ
To determine which objects are live and which are trash, the GC traces reference trees starting from GC Roots:
[ GC Root ] ---> (Live Object A) ---> (Live Object B)
[ Unreachable ] ---> (Dead Object C) ---> (Dead Object D)
Common GC Roots:
-
Thread Stacks: Local variables and parameters in active thread methods.
-
Static Variables: References held by loaded classes in Metaspace.
-
JNI References: C/C++ native pointers created during JNI calls.
-
System Classes: Base classes loaded by the bootstrap classloader.
- Local variables
3. The Garbage Collection Execution FlowΒΆ
JVM garbage collection leverages the Weak Generational Hypothesis: Most objects die shortly after allocation.
+-------------------------------------------------------+-----------------------+
| YOUNG GENERATION | OLD GENERATION |
| +--------------------+-------+-------+ | (Tenured) |
| | Eden Space | S0 | S1 | | |
| +--------------------+-------+-------+ | |
+-------------------------------------------------------+-----------------------+
Generational Memory Flow & Promotion (Type of GC)ΒΆ
-
Eden Allocation: All newly created objects enter Eden.
-
Minor GC Trigger: When Eden fills up, a Minor GC triggers:
-
Reachable objects in Eden are moved to Survivor Space S0 (or S1).
-
Unreachable objects are swept away immediately.
-
-
Survivor Swapping: On subsequent Minor GCs, surviving objects bounce between S0 and S1. Each survival increments the object's age counter.
-
Promotion to Old Generation: When an object survives a set threshold (default age threshold is typically 15, configurable via
-XX:MaxTenuringThreshold), it is promoted to the Old Generation. -
Major / Full GC: When the Old Generation reaches capacity, a Major GC (or Full GC) runs. It scans the Old Generation (and optionally Metaspace) to clear long-lived dead objects.
Stop-the-World (STW) Pauses: During certain GC phases, all application threads are paused so the GC can safely move or clear objects without race conditions. Reducing STW duration is the primary goal of modern collectors.
4. Core GC AlgorithmsΒΆ
Different phases of garbage collection use distinct strategies to clean and organize memory:
[ Mark & Sweep ] [ Copying ] [ Mark-Compact ]
[X][ ][X][ ] [Live A][Live B] -> [Copy] [Live A][Live B][ ][ ]
(Leaves holes) (Fast, needs extra space) (No fragmentation)
-
Mark and Sweep:
-
Mark: Traverses from GC roots and marks all live objects.
-
Sweep: Scans the heap and reclaims memory occupied by unmarked objects. Leaves fragmented memory gaps.
-
-
Copying:
- Moves all live objects from one space (e.g., Eden) to a clean target space (e.g., S0/S1). Clears the original space completely. Fast and fragmentation-free, but requires spare memory.
-
Mark-Compact:
- Marks live objects, then relocates ("compacts") them to the start of the memory space to create a continuous block of free memory. Eliminates fragmentation in Old Generation.
-
Generational Collection:
- Combines Copying (for Young Gen) and Mark-Compact/Sweep (for Old Gen) for optimal performance.
5. JVM Garbage Collector ImplementationsΒΆ
Depending on application requirements (throughput vs. ultra-low latency), Java provides several collectors: | Collector | Threading Model | Recommended Application Use Case | Key JVM Flag | |----------------|---------------------|--------------------------------------|------------------| | Serial GC | Single-threaded | Small applications, desktop/client applications, or systems with a small heap (<100 MB). Best for single-core environments where simplicity is preferred over throughput. | -XX:+UseSerialGC | | Parallel GC | Multi-threaded | Batch processing, scientific computing, and CPU-intensive workloads where maximizing throughput is more important than minimizing pause times. | -XX:+UseParallelGC | | G1 GC (Default) | Concurrent, Region-based | General-purpose server applications, web services, and enterprise applications with medium-to-large heaps (4 GBβ64 GB). Provides predictable pause times and balanced throughput. (Default collector since Java 9). | -XX:+UseG1GC | | ZGC | Concurrent, Ultra-Low Latency | Large-scale applications requiring extremely low pause times (typically <1 ms), such as real-time analytics, financial trading systems, and applications with heaps ranging from hundreds of MBs to multiple TBs. Supports Generational ZGC in modern JDKs. | -XX:+UseZGC-XX:+ZGenerational | | Shenandoah GC | Concurrent, Ultra-Low Latency | Applications where consistent low latency is more important than maximum throughput. Performs concurrent compaction to minimize Stop-The-World (STW) pauses, making it suitable for interactive and cloud-native services. | -XX:+UseShenandoahGC |
6. When Does GC Run?ΒΆ
Garbage Collection timing is decided by the JVM.
Common triggers:
- Heap is almost full
- Young Generation becomes full
- Old Generation reaches threshold
- Allocation failure
System.gc()(only a request)
The JVM decides the exact timing.
7. Manual GC Requests & Deprecated FeaturesΒΆ
Invoking Garbage Collection (System.gc())ΒΆ
Calling System.gc() requests that the JVM run garbage collection:
Java
System.gc(); // Requests JVM to execute GC
-
Reality: It is only a suggestion. The JVM execution engine can choose to ignore or delay the request.
-
Best Practice: Avoid invoking
System.gc()in production codeβit can trigger unnecessary full Stop-The-World pauses. Disable explicit calls via-XX:+DisableExplicitGC.
The finalize() Method (Deprecated)ΒΆ
Historically, the finalize() method allowed clean-up routines right before an object was reclaimed:
Java
@Deprecated
protected void finalize() throws Throwable {
// Cleanup native resources
}
-
Why it is deprecated:
finalize()causes performance penalties, unpredictable execution timing, and potential object resurrection bugs. -
Modern Replacement: Use
AutoCloseablewith try-with-resources blocks or thejava.lang.ref.CleanerAPI.
8. GC Logs, Flags, & Performance TuningΒΆ
Useful JVM FlagsΒΆ
Bash
# Set heap allocation boundaries
-Xms2g # Initial heap size
-Xmx8g # Maximum heap size
# Select GC Collector Engine
-XX:+UseG1GC
# Enable Unified GC Logging (Java 9+)
-Xlog:gc*
Deciphering Unified GC Log OutputΒΆ
Plaintext
[0.123s][info][gc] Using G1
[2.495s][info][gc,start] GC(0) Pause Young (Normal)
[2.789s][info][gc,heap ] Eden: 10240K->0K(25600K)
[2.788s][info][gc,heap ] Survivor: 1014K->1024K(5120K)
[2.788s][info][gc,heap ] Old: 20480K->20480K(51200K)
[2.788s][info][gc,metaspace] Metaspace: 12345K->12345K(105678K)
[2.788s][info][gc ] GC(0) Pause Young (Normal) 24M->20M(79M) 333.12ms
-
GC(0) Pause Young (Normal): Indicates a Minor GC cycle on the Young Generation. -
Eden: 10240K->0K(25600K): Eden space occupancy dropped from 10.2 MB to 0 MB out of a 25.6 MB capacity. -
24M->20M(79M): Total heap usage decreased from 24 MB to 20 MB out of 79 MB committed heap. -
333.12ms: Total Stop-The-World pause duration for this GC cycle.
9. Best Practices for Memory OptimizationΒΆ
-
Avoid Memory Leaks:
-
Close resources (database connections, files, sockets) using
try-with-resources. -
Clear static collections and unregister event listeners when components are destroyed.
-
-
Size Collections Properly:
- Initialize collections with expected capacities (
new ArrayList<>(1000)) to prevent array expansion overhead.
- Initialize collections with expected capacities (
-
Minimize Unnecessary Object Allocation:
- Reuse heavy objects, favor immutable objects, and avoid object allocation inside tight, high-frequency loops.
-
Tune Heap Size Realistically:
- Set initial (
-Xms) and maximum (-Xmx) heap sizes to identical values in production servers to avoid runtime heap resizing overhead.
- Set initial (
-
Monitor Production Environments:
- Inspect heap dumps and analyze GC logs using tools like JDK Mission Control, VisualVM, or GCPlot.
10. Useful Monitoring ToolsΒΆ
- JVisualVM
- JConsole
- Java Flight Recorder (JFR)
- Java Mission Control (JMC)
- Eclipse MAT
- GC logs