Files
javademos/docs/02-g1-default.md

57 lines
3.4 KiB
Markdown

# 2. G1 becomes the default, even on one CPU (JEP 523)
Prev: [1. What shipped](01-what-shipped.md) · Next: [3. Compact object headers](03-compact-headers.md)
## The old rule
Since JDK 9 the JVM has chosen its collector by "server-class machine" ergonomics: G1 if it sees at least **two** CPUs and roughly **1792 MB**
of memory, Serial otherwise. A container limited to one CPU, or to 1 GB, quietly got Serial. JEP 523 removes the exception: G1 is chosen
in every environment.
## What the containers say
[`scripts/g1-default.sh`](../scripts/g1-default.sh) runs [`GcReport`](../g1-container/src/GcReport.java) in `amazoncorretto:26` and `:27`
containers. Only the JDK version and the limits change.
| Container | JDK 26 picks | JDK 27 picks | Transcript |
|---|---|---|---|
| `--cpus=1 --memory=2g` | Serial (`UseSerialGC ... ERGONOMIC`) | G1 | [01](output/01-g1-default-1cpu-vs-2cpu.txt) |
| `--cpus=2 --memory=2g` | G1 | G1 | [01](output/01-g1-default-1cpu-vs-2cpu.txt) |
| `--cpus=2 --memory=1g` | Serial | G1 | [02](output/02-g1-default-memory.txt) |
So both halves of the old rule are gone: CPU count *and* memory limit. An explicit `-XX:+UseSerialGC` still wins
([03](output/03-g1-default-optout.txt)); its origin shows as `VM_CREATION`, not `ERGONOMIC`.
## What it costs when your container is small
[`Workload`](../g1-container/src/Workload.java) allocates 50,000 short-lived objects per batch, keeps about 1% of them, and reports wall time, GC
count and time, the slowest single batch and peak RSS. Three runs per configuration, 1 CPU, 2 GB ([04](output/04-g1-cost-1cpu.txt)):
| 4000 batches | wall time | GC count / total GC time | slowest batch | peak RSS |
|---|---|---|---|---|
| 26, default (Serial) | 6.0-6.4 s | 1275 / ~1.85 s | 115-142 ms | 557 MB |
| 27, default (G1) | 8.2-8.4 s | 140 / ~1.98 s | 35-41 ms | 547 MB |
| 27, `-XX:+UseSerialGC` | 5.0-5.7 s | 1264 / ~1.66 s | 119-128 ms | 557 MB |
The shape, not the digits: on one CPU, G1 has roughly **3x shorter worst-case stalls** and takes **about a third longer** to finish the same allocation-heavy work.
G1's concurrent work has to share the only CPU with your application.
A short-lived process makes it look worse ([05](output/05-g1-cost-1cpu-short.txt), 400 batches): 26 finishes in ~0.53 s using ~102 MB RSS; 27 with G1 takes
~1.0 s and touches ~492 MB, because G1 grows the heap toward the 512 MB default maximum instead of collecting early. Serial on 27 matches 26 (~0.52 s, ~98 MB).
## Two suspects, tested
[`scripts/g1-tuning.sh`](../scripts/g1-tuning.sh) ([06](output/06-g1-tuning-1cpu.txt)):
1. JDK 27 also changed the heap free ratios G1 uses (`MinHeapFreeRatio` 40 -> 0, `MaxHeapFreeRatio` 70 -> 100). Restoring 40/70 changed nothing measurable.
2. G1 being allowed to grow toward `MaxHeapSize`. Capping the heap at `-Xmx256m` cut peak RSS from ~492 MB to ~294 MB with about the same wall time.
## Advice
* Nothing to do if you already set a collector explicitly.
* If you were relying on the implicit Serial in small containers and you are throughput- or footprint-bound, pin it: `-XX:+UseSerialGC`.
* If you are happy with G1, **set `-Xmx`** (or `-XX:MaxRAMPercentage`) so the heap does not sprawl to the default maximum.
* Note `-XX:InitiatingHeapOccupancyPercent` is deprecated in 27 in favour of `-XX:G1IHOP` ([60](output/60-removed-options.txt)).
Prev: [1. What shipped](01-what-shipped.md) · Next: [3. Compact object headers](03-compact-headers.md)