Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
3.4 KiB
2. G1 becomes the default, even on one CPU (JEP 523)
Prev: 1. What shipped · Next: 3. Compact object headers
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 runs GcReport 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 |
--cpus=2 --memory=2g |
G1 | G1 | 01 |
--cpus=2 --memory=1g |
Serial | G1 | 02 |
So both halves of the old rule are gone: CPU count and memory limit. An explicit -XX:+UseSerialGC still wins
(03); its origin shows as VM_CREATION, not ERGONOMIC.
What it costs when your container is small
Workload 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):
| 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, 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
- JDK 27 also changed the heap free ratios G1 uses (
MinHeapFreeRatio40 -> 0,MaxHeapFreeRatio70 -> 100). Restoring 40/70 changed nothing measurable. - G1 being allowed to grow toward
MaxHeapSize. Capping the heap at-Xmx256mcut 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:InitiatingHeapOccupancyPercentis deprecated in 27 in favour of-XX:G1IHOP(60).
Prev: 1. What shipped · Next: 3. Compact object headers