Definition
- G1 (Garbage First) – a server-style, mostly-concurrent, region-based garbage collector that has been the default since Java 9.
- Regions – equal-sized heap chunks (1-32 MB) dynamically assigned roles of Eden, Survivor, Old, or Humongous.
- Young collection – a stop-the-world pause that evacuates live objects from Eden and Survivor regions.
- Concurrent Marking – a mostly-concurrent phase that identifies which Old regions are richest in garbage.
- Mixed collection – a pause that evacuates Young regions plus a selected slice of the trashiest Old regions.
- MaxGCPauseMillis – a soft pause-time target (default 200ms) that G1 uses to size the young generation.
- IHOP – InitiatingHeapOccupancyPercent (default 45%), the threshold that triggers concurrent marking.
Imagine the bank's giant document warehouse is split into hundreds of identical small rooms. A cleaning crew has a strict rule: you get ten minutes, clean the rooms that are mostly trash first. They scan which rooms have the most throwaway paper and empty those first, because that frees the most space for the least effort. That is G1: small equal rooms (regions), a time budget (pause target), and a greedy most-garbage-first strategy.
Understanding the Concepts
What are G1 regions?
G1 splits the heap into equal-sized regions, typically between 1 and 32 MB, chosen so there are roughly 2048 regions. Unlike older collectors, Young and Old generations are not contiguous memory areas; they are just sets of regions that can be anywhere in the heap.
# Set region size explicitly (power of 2, 1-32 MB)
java -XX:+UseG1GC \
-XX:G1HeapRegionSize=8m \
-Xmx16g \
-jar transfer-service.jar
# Check region size
java -XX:+UseG1GC -Xmx16g -XX:+PrintFlagsFinal -version 2>&1 \
| grep G1HeapRegionSize
uintx G1HeapRegionSize = 8388608 (8 MB)
How does a Young collection work?
A Young collection is a short stop-the-world pause that evacuates live objects out of Eden and Survivor regions into new Survivor or Old regions, then frees the emptied regions entirely.
Before Young GC:
[Eden][Eden][Eden][Surv][Old][Old][Free][Free]
During Young GC (STW):
- Copy live objects from Eden → Survivor
- Copy aged Survivor objects → Old
- Free emptied Eden and Survivor regions
After Young GC:
[Free][Free][Free][Surv][Old][Old][Free][Free]
↑ freed ↑ promoted
# GC log showing a Young collection
# [0.234s] GC(0) Pause Young (Normal) 24M->8M(256M) 3.456ms
How does Concurrent Marking work?
Concurrent Marking runs mostly alongside the application to find which Old regions hold the most garbage. It starts at the Initial Mark (piggybacked on a young pause), then runs concurrent phases.
Concurrent Marking phases:
1. Initial Mark (STW, piggybacked on Young GC)
2. Root Region Scan (concurrent)
3. Concurrent Mark (concurrent, walks object graph)
4. Remark (STW, short)
5. Cleanup (STW, identifies empty regions)
# IHOP controls when concurrent marking starts
java -XX:+UseG1GC \
-XX:InitiatingHeapOccupancyPercent=45 \
-Xmx8g \
-jar transfer-service.jar
# Modern G1 adjusts IHOP adaptively
# Manual setting is rarely needed
What is a Mixed collection and why is it "Garbage First"?
After marking knows the trash-richest Old regions, G1 collects all Young regions plus a chosen slice of the most-garbage Old regions in the same pause. This is literally "garbage first": it goes after the regions that give the most reclaimed space for the least work.
Mixed collection:
[Eden][Eden][Surv][Old-90%trash][Old-80%trash][Old-10%trash][Old-5%trash]
G1 picks the trashiest Old regions:
Evacuate: Eden + Survivor + Old-90% + Old-80%
Skip: Old-10% and Old-5% (not worth the effort this round)
After:
[Free][Free][Surv][Free][Free][Old-10%][Old-5%]
What is a Full GC and when does it happen?
A Full GC is the fallback nobody wants: a whole-heap, stop-the-world compaction that happens when G1 cannot keep up, usually because of allocation pressure or humongous-object churn.
# Alert on any Full GC in production
# In GC log: [2.345s] GC(5) Pause Full (Allocation Failure) 4096M->2048M 8.234s
# Full GC signals the collector is losing
# Investigate: is the heap too small? Too many humongous objects?
package com.bank.monitoring;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
public class GcMonitor {
public static void checkForFullGc() {
for (GarbageCollectorMXBean gc :
ManagementFactory.getGarbageCollectorMXBeans()) {
if (gc.getName().contains("Full")) {
long count = gc.getCollectionCount();
if (count > 0) {
System.out.println("WARNING: " + count
+ " Full GCs detected on " + gc.getName());
}
}
}
}
public static void main(String[] args) {
checkForFullGc();
}
}
How does MaxGCPauseMillis work?
MaxGCPauseMillis is a soft goal, default 200ms. G1 sizes the young generation and the number of Old regions in each mixed collection to try to hit that target, trading throughput for latency.
# Set a 100ms pause target
java -XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-Xmx8g \
-jar transfer-service.jar
# WARNING: setting it too low starves throughput
# because young generations shrink and collections become too frequent
java -XX:+UseG1GC \
-XX:MaxGCPauseMillis=10 \
-Xmx8g \
-jar transfer-service.jar # May GC so often it hurts throughput
What are common mistakes?
The most common mistake is setting MaxGCPauseMillis too aggressively, which forces tiny young generations and overly frequent collections.
# WRONG: 10ms target on 8 GB heap with G1
java -XX:+UseG1GC -XX:MaxGCPauseMillis=10 -Xmx8g -jar app.jar
# Young gen shrinks to tiny size, GC runs constantly, throughput drops
# CORRECT: if you need sub-10ms pauses, use ZGC, not aggressive G1
java -XX:+UseZGC -Xmx8g -jar app.jar
What should a senior engineer know?
A senior engineer verifies G1 health by reading GC logs and alerting on Full GC and on the pause 99th percentile. At National Bank of India, the payment-enrichment service ran almost entirely on Young collections because most objects died within milliseconds, while a handful of cached customer profiles lived for hours in Old regions that mixed collections cleaned up without ever forcing a Full GC.
# Enable detailed GC logging
java -Xlog:gc*,gc+heap=debug:file=gc.log:time,level,tags:filecount=5,filesize=100m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx8g \
-jar transfer-service.jar
# Parse pause times
grep "Pause" gc.log | awk -F'[ ms]' '{print $(NF-1)}' | sort -n | tail -10
Visual Overview
G1 Heap layout (regions):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│Eden │Eden │Eden │Surv │ Old │ Old │ Old │Free │
│ │ │ │ │ 90% │ 30% │ 10% │ │
│ │ │ │ │trash│trash│trash│ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
G1 lifecycle:
┌──────────────┐
│ Young GC │ ← evacuate Eden + Survivor (short STW)
└──────┬───────┘
│ heap crosses IHOP (45%)
▼
┌──────────────┐
│ Concurrent │ ← mark live objects (mostly concurrent)
│ Marking │
└──────┬───────┘
│ marking complete
▼
┌──────────────┐
│ Mixed GC │ ← Young + trashiest Old regions (STW)
└──────┬───────┘ "Garbage First" = pick highest-waste regions
│
│ if G1 falls behind:
▼
┌──────────────┐
│ Full GC │ ← whole-heap compaction (BAD, avoid!)
└──────────────┘
Key Takeaway
G1 divides the heap into many equal-sized regions and assigns each region a role dynamically. Young collections evacuate Eden regions in short pauses. Concurrent Marking identifies which Old regions are richest in garbage. Mixed collections then evacuate Young regions plus the trashiest Old regions in one pause: that greedy selection is what makes it Garbage First.
The primary tuning knob is MaxGCPauseMillis, a soft target that G1 uses to size the young generation and the mixed collection set. IHOP controls when concurrent marking starts, and modern G1 adapts it automatically. A Full GC is the fallback that signals G1 has fallen behind.
At National Bank of India, the payment-enrichment service ran almost entirely on cheap Young collections, with occasional mixed collections cleaning up long-lived cached profiles, and never triggered a Full GC. The key was right-sized heap, a reasonable pause target, and monitoring the GC log.
Remember one sentence: G1 is garbage first because it greedily collects the regions with the most reclaimable space, staying inside a pause-time budget you set with MaxGCPauseMillis.
---