Java Memory Management: Beyond the Basics
While Java handles memory automatically via Garbage Collection (GC), high-performance engineering requires understanding the **allocation path**, **reclamation barriers**, and **internal heap structures**.
I. The Allocation Path: TLABs and Bump-the-Pointer
Most developers assume allocation happens on a global heap lock. In reality, the JVM uses **Thread-Local Allocation Buffers (TLABs)** to avoid contention.
* **Mechanics:** Each thread is assigned a small chunk of the Eden space. Allocation is a simple **pointer increment** (Bump-the-Pointer) within that private buffer.
* **The Humongous Path:** Objects larger than ~50% of a G1 region bypass TLABs and Eden entirely, landing in **Humongous Regions**. These are expensive because they are often collected only during Full GC or specific G1 phases.
II. Generational Mechanics and Barriers
The **Generational Hypothesis** states that most objects die young. Modern GCs use **Write Barriers** to track cross-generational references.
* **Card Tables:** When a reference in the Old Generation is updated to point to a Young Generation object, the JVM "dirties" a card in a bitmask. This allows the Young GC to find "root" references from the Old Gen without scanning it entirely.
* **Promotion (Tenuring):** Objects surviving several Minor GCs (threshold controlled by `-XX:MaxTenuringThreshold`) are moved to the Old Generation.
III. Modern GC Comparison: G1 vs. ZGC
| Feature | G1 GC (Balanced) | ZGC (Latency Focused) |
| :--- | :--- | :--- |
| **Heap Structure** | Regions (fixed size) | Regions (dynamic size) |
| **Max Pause Time** | Target-based (~200ms) | Sub-millisecond |
| **Throughput** | High | Medium (due to load barriers) |
| **Best For** | General apps, large heaps | Low-latency, huge heaps (>32GB) |
IV. Concrete Diagnostic Scenario: The "Slow Leak"
**Problem:** Application throughput degrades over 48 hours. `jstat` shows the Old Generation is steadily climbing despite frequent GCs.
Step 1: Monitor Allocation vs. Reclamation
```bash
Watch GC stats every 1s
jstat -gcutil <pid> 1000
```
If `O` (Old Gen %) increases after every Full GC, you have a **Memory Leak** (retained references).
Step 2: Sample for Hotspots
Using `jcmd` to identify which classes are hogging memory:
```bash
Print top 20 classes by memory usage
jcmd <pid> GC.class_histogram | head -n 20
```
Step 3: Deep Analysis
If a custom class (e.g., `com.app.SessionCache`) appears at the top, capture a heap dump for the **Eclipse Memory Analyzer (MAT)**:
```bash
jcmd <pid> GC.heap_dump /tmp/dump.hprof
```
V. Critical Tuning Parameters
1. **-Xms / -Xmx:** Always set these equal in production to prevent resizing pauses.
2. **-XX:+UseContainerSupport:** Mandatory for Docker to ensure the JVM respects cgroup limits.
3. **-XX:MaxDirectMemorySize:** Controls off-heap allocation (used by Netty/NIO). If not set, it defaults to `-Xmx`, which can lead to OS-level OOMs.
---
**See Also:**
- [Performance Profiling](PerformanceProfiling) — Tools for memory analysis.
- [Java Concurrency](JavaConcurrencyPatterns) — Impact of Virtual Threads on memory.
- [Memory Architectures](MemoryArchitectures) — How JVM maps to physical RAM.