Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
57 lines
3.4 KiB
Markdown
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)
|