The 30 second answer
ZGC is the low-pause collector that does nearly all its work while the application keeps running. Java 21 added a generational mode that keeps young and old objects apart, so it can collect short-lived objects often and cheaply. In Java 21 you have to turn it on with an extra flag next to the normal ZGC flag. Later releases made generational the default and then the only mode.
Most objects die young. Non-generational ZGC scanned the whole heap every cycle, so with a high allocation rate it needed more heap headroom and could hit allocation stalls. JEP 439 lists the goals: lower risk of allocation stalls, lower heap memory overhead and lower GC CPU overhead than non-generational ZGC, while keeping pauses very short.
| JDK | Generational ZGC |
|---|---|
| 21 | opt in: -XX:+UseZGC -XX:+ZGenerational (plain -XX:+UseZGC is still non-generational) |
| 23 | default for -XX:+UseZGC (JEP 474) |
| 24 | non-generational mode removed (JEP 490) |
Sources: JEP 439: Generational ZGC, JEP 474: ZGC Generational Mode by Default, JEP 490: Remove the Non-Generational Mode, GC Tuning Guide: ZGC