JVM Memory Model, Garbage Collection, and Optimization Guide
This article explains the JVM memory architecture, object lifecycle, young and old generation garbage‑collection mechanisms, tuning goals such as low latency, high throughput and large heap, and provides practical advice on monitoring, common memory‑related problems, and choosing appropriate collectors for different application scenarios.
Before tuning the JVM, remember that it is designed to relieve developers from manual memory management; performance problems should first be examined at the code and design level, then at middleware bottlenecks, and only finally at JVM parameters.
01 JVM Memory Model
The Java 8 memory model consists of the heap (young and old generations), method area, program counter, Java stack, and native stack. The heap is further divided into Eden, Survivor S0/S1, and the old generation. A diagram of the model is shown in the original article.
Object lifecycle in the JVM proceeds as follows:
.java source is compiled by javac into a .class file.
The class file is loaded by the class loader (parent‑delegation model) into the metaspace.
When an object is created, memory is allocated in the heap, usually in Eden.
After a Young GC (YGC), surviving objects are moved to Survivor S0; subsequent YGCs promote them to S1 and eventually to the old generation.
Unreferenced objects are reclaimed by the garbage collector during the next GC cycle.
1.1 Young Generation Collection Principle
When Eden becomes full, a YGC stops all application threads (Stop‑the‑World), moves surviving objects to Survivor S0, and clears Eden. The next YGC may move objects from S0 to S1 and clear both Eden and S0, repeating this process.
1.2 When Do Objects Move to the Old Generation?
1.2.1 Object Age – By default, an object that survives 15 YGCs is promoted; this threshold can be changed with -XX:MaxTenuringThreshold.
1.2.2 Dynamic Age Determination – The JVM tracks the total size of objects per age in Survivor space; if the cumulative size of objects up to a certain age exceeds half of Survivor, all objects of that age and older are promoted regardless of the static age threshold.
1.2.3 Large Object Direct Promotion – Objects larger than the -XX:PretenureSizeThreshold are allocated directly in the old generation to avoid fragmentation in the young generation.
1.2.4 Temporary Promotion – If Survivor space cannot hold all survivors after a YGC, objects are promoted to the old generation even if they have not reached the age threshold.
1.3 Old Generation GC Trigger Timing
When the old generation becomes full, a Full GC (FGC) is triggered, which also performs an Old GC and often a concurrent YGC. The exact trigger depends on the garbage collector in use.
Serial/Parallel Old – FGC occurs when old‑generation space is insufficient.
CMS – Starts when old‑generation occupancy reaches a configurable threshold (default 68%).
G1 – When old‑generation occupancy reaches a heuristic or configured threshold (default ~45%), a Mixed GC is scheduled, collecting selected old‑generation regions together with the entire young generation.
02 JVM Optimization Objectives
Low Latency – Short GC pause times.
High Throughput – Maximize the amount of work done by application code versus GC.
Large Heap – Support big memory allocations for data‑intensive workloads, at the cost of more complex GC.
These goals often conflict; achieving one may compromise the others.
2.2 How to Balance the Objectives
Web / Micro‑services – Low Latency First – Prefer collectors such as CMS (deprecated), ZGC, or Shenandoah for minimal pause times; G1 is a good fallback for large heaps.
Big Data / Scientific Computing – High Throughput First – Use Parallel GC to maximize CPU utilization.
Large‑Memory Applications – Large Heap Management First – Choose G1 GC or ZGC, which handle big heaps efficiently while providing reasonable latency.
3 Practical Tuning Scenarios
3.1 New Application Load Test – Observe YGC/FGC frequency and duration, post‑YGC survivor rates, and old‑generation growth using jstat.
3.2 Existing Application Issues
OutOfMemoryError (heap) – caused by excessive object creation, memory leaks, oversized caches, or large arrays.
Metaspace overflow – caused by loading too many or too large classes; increase -XX:MaxMetaspaceSize if necessary.
Stack or native‑method‑stack overflow – caused by deep recursion or too many threads; adjust -Xss or limit thread count.
Direct buffer memory overflow – caused by misuse of ByteBuffer.allocateDirect or NIO frameworks; tune -XX:MaxDirectMemorySize.
Additional symptoms such as frequent FGC, long GC pauses, or continuous old‑generation growth should be investigated according to the root‑cause categories above.
Summary
Effective JVM tuning is context‑specific; avoid unnecessary tuning if the application runs well, but always monitor key JVM metrics. Aim to keep post‑YGC survivor size below 50 % of Survivor space, minimize promotions to the old generation, and reduce the frequency of Full GCs.
Continuous observation, root‑cause analysis, and a solid understanding of the JVM internals will improve both performance and confidence in production systems.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
