Why this matters
- The generational layout is why garbage collection is fast at all, and it rests on one empirical observation about how objects behave.
OutOfMemoryErrorhas several variants naming different regions, and the region tells you what kind of problem you have.- Tuning flags are meaningless until you know which space each one resizes.
The regions
- Young generation — where every new object is allocated. Subdivided into Eden and two survivor spaces.
- Old generation, also called tenured — objects that have survived several young collections.
- Metaspace — class metadata: field and method definitions, bytecode, the runtime constant pool. Native memory, outside the heap.
- Code cache — native code produced by the JIT compiler.
- Thread stacks — one per thread, holding frames. Native memory, not heap.
The weak generational hypothesis
The entire design rests on one observation that holds for almost all programs:
That is why a minor collection of a 500 MB young generation can take a few milliseconds: if only 2% of it survives, there are only 10 MB of objects to copy.
Eden and the survivor spaces
Young generation
┌──────────────────────────┬────────┬────────┐
│ Eden │ S0 │ S1 │
└──────────────────────────┴────────┴────────┘
new objects here one in use, one empty
The cycle
- All allocation happens in Eden, by bumping a pointer — about as cheap as a stack push.
- When Eden fills, a minor collection runs. Live objects are copied into the empty survivor space; Eden is then wholly empty.
- Objects already in a survivor space are copied to the other one, and their age counter increments.
- One survivor space is always empty, which is what makes the copy possible without extra space.
- An object whose age passes the tenuring threshold is promoted to the old generation.
An object is also promoted immediately if it is too large for Eden, or if the target survivor space fills up.
void process() {
for (int i = 0; i < 1_000_000; i++) {
String temp = "item-" + i; // allocated in Eden, dead by the next iteration
use(temp);
}
}
A million strings, almost all collected by cheap minor collections. Nothing reaches the old generation.
Minor and major collections
| Aspect | Minor (young) collection | Major (old) collection |
|---|---|---|
| Region | Eden and survivor spaces | Old generation, often the whole heap |
| Frequency | Often — many times a minute | Rarely |
| Duration | Milliseconds | Tens of milliseconds to seconds |
| Algorithm | Copying — cost scales with survivors | Mark-sweep-compact or concurrent marking |
| Pause | Short stop-the-world | Longer, or mostly concurrent on modern collectors |
| Triggered by | Eden filling up | Old generation occupancy crossing a threshold |
Region
Minor (young) collectionEden and survivor spacesMajor (old) collectionOld generation, often the whole heapFrequency
Minor (young) collectionOften — many times a minuteMajor (old) collectionRarelyDuration
Minor (young) collectionMillisecondsMajor (old) collectionTens of milliseconds to secondsAlgorithm
Minor (young) collectionCopying — cost scales with survivorsMajor (old) collectionMark-sweep-compact or concurrent markingPause
Minor (young) collectionShort stop-the-worldMajor (old) collectionLonger, or mostly concurrent on modern collectorsTriggered by
Minor (young) collectionEden filling upMajor (old) collectionOld generation occupancy crossing a threshold
Frequent cheap collections plus rare expensive ones is the whole bargain.
Metaspace
Class metadata lives in native memory, sized on demand rather than fixed.
// Metaspace holds, per class: the name, field and method definitions,
// bytecode, the constant pool, and the vtable for dynamic dispatch.
Before Java 8 this was PermGen, a fixed-size region inside the heap. Overflowing it was a familiar failure when redeploying an application server repeatedly, because each redeploy loaded a fresh copy of every class and the old ones were not always collectible.
What changed in Java 8
- PermGen was removed and replaced by metaspace in native memory.
- Metaspace grows automatically, so
OutOfMemoryError: PermGen spacelargely disappeared. - Interned strings and static fields moved to the heap, where they can be collected normally.
- A runaway metaspace is still possible — set
-XX:MaxMetaspaceSizeto cap it, so a class-loader leak fails fast instead of consuming all system memory.
The flags worth knowing
# Heap size — set both to the same value in production to avoid resize pauses
-Xms2g -Xmx2g
# Young generation share of the heap
-XX:NewRatio=2 # old : young = 2 : 1, so young is one third
-Xmn512m # or set the young size directly
# Eden : survivor ratio
-XX:SurvivorRatio=8 # Eden : each survivor = 8 : 1
# Per-thread stack size
-Xss512k
# Cap metaspace so a class-loader leak fails fast
-XX:MaxMetaspaceSize=256m
# Produce a heap dump automatically when the heap is exhausted
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app
# Observe what is actually happening
-Xlog:gc*:file=gc.log:time,uptime,level,tags
Observing the regions
# A live summary, updated every second
jstat -gcutil <pid> 1000
# S0 S1 E O M CCS YGC YGCT FGC FGCT
# 0.00 48.19 72.31 31.04 94.21 91.88 142 0.891 2 0.214
The columns are survivor 0 and 1, Eden, old, metaspace and compressed class space as percentages, then young collection count and total time, then full collection count and total time.
Reading it
- Eden rising and resetting repeatedly is normal — that is minor collection working.
- Old generation climbing steadily and never falling after major collections is the signature of a leak.
- A high
FGCcount means full collections are frequent, which usually means the old generation is too small or something is being promoted that should not be. - Metaspace near 100% points at class loading, not at your objects.
Common misreadings
- "The heap is split by object type." It is split by age. A
Stringand aConnectionboth start in Eden. - "Objects are allocated in the old generation if they are large." Only if they are too large for Eden. Size is not the usual criterion; survival is.
- "Metaspace is part of the heap." It is native memory.
-Xmxdoes not bound it. - "PermGen still exists." It was removed in Java 8.
- "Both survivor spaces hold objects." One is always empty; that is what makes copying collection possible.
- "A major collection collects the young generation too." A full collection does. "Major" conventionally refers to the old generation, and the terms are used loosely enough that it is worth being explicit.
Quick recall
Everything you need if you only revisit this box.
- The heap is generational: young (Eden + two survivors) and old. Division is by age, not type.
- The weak generational hypothesis — most objects die young — is why a minor collection is cheap: cost scales with survivors, not garbage.
- Allocation happens in Eden via a thread-local allocation buffer, so it is a pointer bump with no locking.
- One survivor space is always empty. Surviving objects are copied across and age; past the tenuring threshold they are promoted.
- Minor collections are frequent and short; major ones are rare and longer.
- Metaspace holds class metadata in native memory and replaced PermGen in Java 8. Its exhaustion means a class-loader leak.
- Always set
-XX:+HeapDumpOnOutOfMemoryErrorand GC logging. Usejstat -gcutilto watch the regions. - A steadily rising old generation that never drops after a full collection is the signature of a leak.
Test yourself
Answer these before moving on — recall is what makes it stick.