jdk 27 起未显式指定 gc 时一律默认使用 g1;jdk 23 起 zgc 成为默认(需满足堆>4gb等条件),jdk 21 及更早仍默认 g1。

升级 OpenJDK 后默认 GC 变了,最直接的判断方式是:不加 -XX:+UseG1GC 或其他收集器开关时,java -version 对应的 JDK 版本决定了它选谁——JDK 27 起一律 G1,JDK 23 起 ZGC 已成默认(但尚未全量落地),而 JDK 21 及更早仍维持 G1 默认。
怎么确认当前 JVM 实际用了哪个 GC
别信文档、别猜配置,用 -Xlog:gc*:stdout 或 -XX:+PrintGCDetails 启动一次就能看到真实日志头。关键看日志第一行是否含 G1、Z、Serial 或 Parallel 字样。
- 如果启动参数里没显式指定
-XX:+UseXXXGC,JDK 27 就不会出现Serial日志,哪怕你只给了-Xmx256m -XX:ActiveProcessorCount=1 - JDK 23+ 的镜像若未禁用 ZGC(即没加
-XX:-UseZGC),且堆大于 4GB、Linux x64 环境,大概率已走 ZGC;但小堆(如-Xmx1g)仍可能 fallback 到 G1 -
-XX:+PrintCommandLineFlags也能辅助判断:它会打印 JVM 自动推导出的 GC 开关,比如输出-XX:+UseG1GC就说明已锁定 G1
为什么旧脚本在 JDK 27 上突然 GC 行为异常
常见于容器化部署中硬编码「小内存 = Serial」逻辑的运维脚本或 CI 构建流程。JDK 27 不再识别 ActiveProcessorCount=1 或 物理内存 这类条件来降级 GC,所有未指定场景都走 G1。
- 现象:Pod RSS 持续偏高、GC 频次下降但单次耗时变长、监控里出现
G1 Evacuation Pause而非Pause Young (Allocation Failure) - 根本原因:G1 的并发标记阶段会常驻线程、占用 CPU,而 Serial 是纯 STW,小容器里更容易被资源配额压制,导致毛刺明显
- 修复动作不是回退 JDK,而是补上明确开关:
-XX:+UseSerialGC(若真需 Serial),或接受 G1 并调-XX:G1HeapRegionSize和-XX:MaxGCPauseMillis
升级前后 GC 表现差异怎么量化对比
不能只看平均停顿,得盯住 P99/P999 GC 暂停时间、堆内存实际占用(RSS)、以及 GC 线程 CPU 使用率这三个维度。
- 用
jstat -gc <pid> 1s</pid>拉 5 分钟数据,重点看GCT(总 GC 时间占比)和YGCT/FGCT是否突增;JDK 27 + G1 下FGCT应趋近于 0,但YGCT可能比 Serial 场景高 10–20% - 对比 RSS:Serial GC 堆外开销小,RSS ≈ heap + metaspace;G1 多了 Remembered Set 和 Concurrent Marking 线程栈,同等堆下 RSS 高 5–15%,小容器里容易触发 OOMKilled
- 若用了 CDS(Class Data Sharing),注意 JDK 27 默认开启紧凑对象头(
-XX:+UseCompactObjectHeaders),旧.jsa文件无法复用,会静默 fallback 到无 CDS 启动,启动变慢且内存占用上升
最容易被忽略的是 CDS 兼容性问题:升级 JDK 后没重建 .jsa,又没检查 -Xlog:cds* 日志,结果服务启动慢了一倍、RSS 却没降下来——这和 GC 无关,但会被误判为 GC 异常。











