Back to JVM Tuning & Troubleshooting
Q5Question 5 of 15

How does G1 GC work?

Key Concepts
  • Divides heap into equal-sized regions (not fixed Young/Old areas)
  • Prioritizes collecting regions with most garbage (Garbage First)
  • Concurrent marking + evacuation pauses
  • Mixed collections: collects both Young and selected Old regions
  • Target pause time: -XX:MaxGCPauseMillis=200 (default)

Definition

G1 chops the heap into regions, marks garbage concurrently, and collects the fullest-of-garbage regions first to hit a pause-time target.

G1 (Garbage First) is a region-based collector that divides the heap into many equal-sized regions instead of fixed contiguous Young and Old areas. Each region plays a role – Eden, Survivor, Old, or Humongous – and roles can change over time. G1 marks live objects concurrently while the application runs, then collects the regions with the most garbage first to reclaim the most space for the least work. It targets a configurable pause-time goal and performs incremental "mixed" collections that clean Young regions plus a few Old regions together, avoiding long Full GCs.

Understanding the Concepts

How does G1 divide the heap?

G1 divides the heap into equal-sized regions, typically between 1 MB and 32 MB each, controlled by -XX:G1HeapRegionSize. By default G1 aims for about 2048 regions and picks a region size automatically based on the heap size, but you can override it. Each region is logically tagged as Eden, Survivor, Old, or Humongous. Humongous regions hold objects larger than half a region, which are allocated specially. Because the generations are now just sets of regions rather than fixed contiguous blocks, G1 can resize them dynamically.

DIAGRAM
  Heap divided into regions (example with 16 regions):
  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  │Eden │Eden │ S0  │ Old │ Old │ Old │Free │Free │
  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
  │Eden │Free │ Old │ Old │Humon│Humon│Free │ S1  │
  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
  Each region can change role dynamically.

How does the Young collection work?

New objects are allocated into Eden regions. When Eden fills, a Young collection (a Minor GC) runs: live objects are evacuated (copied) into Survivor regions, and after enough survivals into Old regions. This evacuation is a stop-the-world pause, but because G1 only processes a chosen set of regions, it can keep the pause short.

What is concurrent marking?

The "Garbage First" idea comes from concurrent marking. While the application runs, G1 performs a concurrent marking cycle that identifies which Old regions contain the most garbage. The marking has several phases:

  1. Initial mark (stop-the-world, piggybacks on a Young GC): marks objects directly reachable from GC roots.
  2. Concurrent mark: traces the object graph while the application runs.
  3. Remark (stop-the-world): finishes marking objects that changed during concurrent mark.
  4. Cleanup (partially stop-the-world): identifies empty regions and prepares for mixed collections.

What are mixed collections?

After marking, G1 performs mixed collections, which collect all Young regions plus a selected subset of the most-garbage Old regions in the same pause, spreading Old Gen cleanup across several incremental collections instead of one giant Full GC.

BASH
# GC log showing a mixed collection
[12.345s][info][gc] GC(15) Pause Young (Mixed) (G1 Evacuation Pause) 1200M->800M(4096M) 18.234ms
#                          ^Mixed = Young + selected Old regions

What JVM flags control G1?

The two most important flags are -XX:MaxGCPauseMillis and -XX:G1HeapRegionSize.

BASH
# Typical G1 configuration for a banking API
java -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=100 \
     -XX:G1HeapRegionSize=4m \
     -XX:InitiatingHeapOccupancyPercent=45 \
     -Xms4g -Xmx4g \
     -jar bank-api.jar

| Flag | Purpose | Default |
|---|---|---|
| -XX:MaxGCPauseMillis | Soft target for max pause time | 200 ms |
| -XX:G1HeapRegionSize | Size of each region | Auto (1-32 MB) |
| -XX:InitiatingHeapOccupancyPercent | When to start concurrent marking | 45% |
| -XX:G1MixedGCCountTarget | How many mixed GCs to spread Old cleanup over | 8 |

MaxGCPauseMillis is a goal, not a guarantee. G1 uses it to decide how many regions to include in each collection: a tighter target means fewer regions per pause. Setting it too low can hurt throughput because G1 collects less per pause and runs more often.

When does a Full GC happen in G1?

A true Full GC in G1 is a single-threaded fallback that collects the entire heap. It happens when allocation outpaces concurrent marking – the application fills regions faster than G1 can identify and clean them. This is called an evacuation failure or allocation failure. It is a sign of trouble, usually the heap being too small. In banking applications at National Bank of India, Rajesh treats any Full GC in the G1 logs as an incident to investigate, because it means the service experienced a multi-second pause that blocked customer requests.

Key Takeaway

G1, which stands for Garbage First, is the default collector from Java 9 and it works by dividing the heap into many equal-sized regions instead of two big contiguous Young and Old areas. Every region is tagged as Eden, Survivor, Old, or Humongous for very large objects, and because the generations are just collections of regions, G1 can resize them dynamically as the workload changes. New objects go into Eden regions, and when those fill, G1 evacuates the survivors into Survivor and eventually Old regions in a short stop-the-world pause.

The clever part is the name. While the application keeps running, G1 does a concurrent marking cycle that figures out which Old regions are mostly garbage. When it collects, it goes after those regions first, because clearing the regions with the most dead objects reclaims the most memory for the least copying effort. It also does mixed collections, cleaning all the Young regions plus a few of the dirtiest Old regions together in each pause, which spreads Old Generation cleanup across many small incremental collections and avoids the long Full GC that older collectors suffered. The two flags I reach for are -XX:MaxGCPauseMillis, which sets a soft target for how long any pause may last, and -XX:G1HeapRegionSize, which sets the region size. I always remind people that the pause target is a goal, not a guarantee, and setting it unrealistically low just makes G1 collect less per pause and run more often, which can hurt throughput.

In my banking work at National Bank of India, G1 is what runs our main online API. Customer traffic is bursty and our account cache grows and shrinks all day, so G1's region-based, self-resizing generations fit perfectly. I set a modest pause target so a transfer never appears to hang, and I watch the GC logs for two things: that pauses stay near the target, and that no true Full GC ever appears. When we had corporate clients generating multi-megabyte statement blobs, those became Humongous allocations, so I tuned the region size to make sure they did not fragment the heap. Understanding that G1 thinks in regions and collects garbage first is what lets me tune it confidently instead of guessing.

Remember one sentence: How does G1 GC work: divides heap into equal-sized regions, collects regions with most garbage first ("Garbage First"), concurrent marking, mixed collections: and what JVM flags control it (-XX:MaxGCPauseMillis, -XX:G1HeapRegionSize) is fundamental knowledge for Java interviews and production banking work.

---

Share
Use ← → arrow keys to navigate