尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JDK 8到JDK 17升级实战:五大垃圾回收器原理与G1/ZGC调优指南

JDK 8到JDK 17升级实战:五大垃圾回收器原理与G1/ZGC调优指南 这次我们来看一个 Java 开发者绕不开的实战话题从 JDK 8 升级到 JDK 17特别是垃圾回收器GC的完整拆解。很多团队还在用 JDK 8但无论是性能、安全还是新特性JDK 17 作为长期支持版本LTS都已经是主流选择。升级的核心挑战之一就是垃圾回收器的变迁——JDK 8 默认的 Parallel Scavenge Parallel Old 组合在 JDK 17 中已被 G1 取代而经典的 CMS 回收器更是被彻底移除。如果你关心升级后的应用性能、停顿时间STW变化或者想知道如何为你的服务选择合适的 GC这篇文章可以直接收藏。我们会先理清五大垃圾回收器Serial, Parallel, CMS, G1, ZGC的核心机制与适用场景再给出从 JDK 8 迁移到 JDK 17 时关于 GC 调优的实操建议和避坑指南。1. 核心能力速览JDK 8 到 JDK 17 的 GC 演变在深入细节之前我们先通过一个表格快速把握这次升级中垃圾回收器的关键变化这决定了你后续的调优方向。能力项JDK 8 典型情况JDK 17 典型情况说明与影响默认 GCParallel Scavenge (Young) Parallel Old (Old)G1 (Garbage-First)JDK 9 起 G1 成为默认。目标是在高吞吐与低延迟间取得更好平衡。CMS 状态可用 (-XX:UseConcMarkSweepGC)已移除JDK 14 中废弃JDK 17 中完全移除。升级时必须切换 GC。新 GC 选项G1 已存在ZGC/Shenandoah 无ZGC、Shenandoah 成为生产可用选项ZGC低延迟、Shenandoah低停顿为超大堆或低延迟场景提供新选择。调优复杂度相对简单参数直观相对复杂需理解新算法G1 的调优逻辑与 Parallel/CMS 不同需要重新学习。目标场景吞吐量优先平衡吞吐与延迟或极致低延迟现代应用更关注响应时间G1/ZGC 的设计更贴合此趋势。升级强制动作无必须移除-XX:UseConcMarkSweepGC参数否则 JVM 无法启动。这是升级时最先要检查的点。简单来说从 JDK 8 升级到 JDK 17在垃圾回收方面你不是在微调而是在换一套引擎。默认的 GC 换了旧的选项没了新的强大工具出现了。理解这五大回收器的原理是做出正确选择的前提。2. 五大垃圾回收器核心原理拆解要做出明智的选择必须理解它们是如何工作的。我们按出现时间和设计目标逐一拆解这五大回收器。2.1 Serial 收集器单线程的奠基者这是最古老、最简单的收集器。它的“单线程”意义深远在进行垃圾回收时必须暂停所有用户线程Stop-The-World, STW并且这个暂停是由单个线程完成的。工作过程新生代回收采用“复制”算法。暂停所有线程单个 GC 线程将 Eden 区和一个 Survivor 区中存活的对象复制到另一个空的 Survivor 区然后清空 Eden 和之前的 Survivor。老年代回收采用“标记-整理”算法。同样暂停所有线程单个 GC 线程标记出所有存活对象然后将它们向内存空间的一端滑动清理掉边界以外的内存。优点实现简单没有线程交互开销在单核处理器或极小堆内存如几十到百兆的场景下效率很高。缺点STW 时间与堆大小成正比不适合现代多核服务器和大内存应用。JDK 8/17 启用参数-XX:UseSerialGC适用场景客户端模式、嵌入式系统、或用于学习理解 GC 原理。生产环境服务器基本不用。2.2 Parallel / Throughput 收集器吞吐量的王者这是 JDK 8 的默认组合目标是最大化应用程序的吞吐量单位时间内处理的业务量。它是 Serial 收集器的多线程并行版本。工作过程新生代Parallel Scavenge多线程并行执行复制算法。老年代Parallel Old多线程并行执行标记-整理算法。两者在进行回收时都会发生 STW但利用多核优势缩短了 STW 的时间。优点吞吐量高适合后台运算、批处理等不太关心个别请求延迟的场景。缺点STW 时间依然不可控在堆较大时一次 Full GC 的停顿可能达到数秒甚至更久。JDK 8/17 启用参数-XX:UseParallelGC(年轻代) 和-XX:UseParallelOldGC(老年代通常自动启用)适用场景科学计算、数据导出、报表生成等吞吐量优先的业务。2.3 CMS 收集器低延迟的尝试已退役Concurrent Mark Sweep 收集器的设计目标是获取最短的回收停顿时间。它允许垃圾收集线程与用户线程并发工作。工作过程复杂分阶段初始标记STW仅标记 GC Roots 直接关联的对象速度很快。并发标记GC 线程与用户线程并发遍历整个对象图进行标记。重新标记STW修正并发标记期间因用户线程运行而产生变动的标记。并发清除GC 线程与用户线程并发清理未被标记的死亡对象。优点大部分标记和清除工作与应用并发STW 时间极短用户体验好。缺点CPU 敏感并发阶段会占用一部分 CPU导致应用吞吐量下降。浮动垃圾并发清理阶段产生的垃圾只能下次 GC 处理。内存碎片采用“标记-清除”算法会产生碎片可能触发 Full GC 进行压缩。调优复杂参数繁多如-XX:CMSInitiatingOccupancyFraction设置触发阈值。JDK 8 启用参数-XX:UseConcMarkSweepGC现状JDK 14 废弃JDK 17 移除。升级时必须替换。历史场景曾经用于对延迟敏感的服务如 Web 服务器。2.4 G1 收集器平衡之选JDK 9 默认Garbage-First 的设计目标是替代 CMS在可控的停顿时间内获得尽可能高的吞吐量。它引入了“Region”的概念将堆划分为多个大小相等的独立区域。核心思想化整为零不再固定分代物理边界每个 Region 可以属于 Eden、Survivor、Old 或 Humongous存放大对象。可预测停顿通过-XX:MaxGCPauseMillis参数设定目标停顿时间如 200msG1 会尽力达成。筛选回收根据每个 Region 的“垃圾价值”回收所需时间与可释放空间优先回收价值高的 RegionGarbage-First 名字由来。工作过程年轻代回收STW多线程并行将 Eden 和 Survivor Region 的存活对象复制到新的 Survivor 或 Old Region。并发标记周期类似 CMS但作用于整个堆用于标记老年代 Region 的存活对象并计算各 Region 的回收价值。混合回收在并发标记周期后G1 会多次进行混合回收不仅回收年轻代 Region还会根据价值选择部分老年代 Region 进行回收。优点兼顾吞吐和停顿可预测停顿时间有效避免内存碎片。缺点内存占用稍高Remembered Set 等数据结构开销调优参数比 Parallel 多。JDK 8/17 启用参数-XX:UseG1GC(JDK 9 后默认)适用场景JDK 9 的通用默认选择适用于大多数服务端应用特别是堆内存较大如 6GB 以上或对停顿时间有要求的服务。2.5 ZGC 收集器极致低延迟的先锋Z Garbage Collector 是 JDK 11 引入的实验特性在 JDK 15 成为生产可用目标是将停顿时间控制在10 毫秒以内且停顿时间不会随堆大小增长而增加。核心技术染色指针将 GC 相关的元数据存储在指针本身而非对象头这减少了内存访问开销。并发处理标记、转移压缩、重定位等几乎所有阶段都是并发执行的STW 时间极短仅用于根节点扫描等必要环节。Region 布局支持动态创建和销毁的 Region更灵活。优点超低停顿亚毫秒到十毫秒级停顿时间与堆大小无关吞吐量损失小。缺点在 JDK 17 中不支持类卸载JDK 21 已支持内存占用较高。JDK 17 启用参数-XX:UseZGC适用场景超大堆内存TB 级别、对延迟极度敏感的核心交易系统、实时计算。需要评估其内存开销和功能限制。Shenandoah是另一个低停顿收集器目标与 ZGC 类似但实现原理不同使用 Brooks 指针。启用参数为-XX:UseShenandoahGC。在 JDK 17 中ZGC 更受 Oracle 官方推荐。3. 从 JDK 8 (CMS/Parallel) 迁移到 JDK 17 (G1) 实战指南理论清楚了现在进入实战。假设你有一个正在使用 JDK 8 和 CMS 收集器的线上服务如何平稳升级到 JDK 173.1 环境准备与前置检查备份与回滚方案这是第一步。确保有完整的代码、配置备份并规划好快速回滚到 JDK 8 的流程。检查依赖兼容性框架/库版本确保 Spring Boot、MyBatis、Netty 等核心框架支持 JDK 17。通常 Spring Boot 2.5 已提供良好支持。第三方 JAR 包某些古老的或使用了内部 API 的 JAR 可能在 JDK 17 上运行失败。使用jdeps工具进行初步分析。# 分析应用 jar 对 JDK 内部 API 的依赖 jdeps --jdk-internals --class-path lib/* your-application.jar移除 NashornJDK 15 移除了 Nashorn JavaScript 引擎。如果项目中使用需迁移到 GraalVM JavaScript 等替代方案。清理废弃的 JVM 参数首要任务删除所有-XX:UseConcMarkSweepGC和-XX:UseParNewGC参数。检查其他废弃参数如-XX:CMSClassUnloadingEnabled等 CMS 相关参数也应移除。启动时 JVM 会警告废弃参数需关注日志。3.2 安装部署与启动方式下载 JDK 17从 Adoptium 原 AdoptOpenJDK或 Oracle 官网下载对应系统的 JDK 17 LTS 版本。配置环境变量更新JAVA_HOME和PATH指向 JDK 17 目录。首次启动无参数先使用 JDK 17 默认设置即 G1GC启动应用进行最基本的冒烟测试。# 假设原来启动命令是 # java -Xms2g -Xmx2g -XX:UseConcMarkSweepGC -jar app.jar # 升级后先简化为 java -Xms2g -Xmx2g -jar app.jar验证启动与功能确保应用能正常启动核心业务流程跑通。3.3 性能基准测试与 GC 日志分析升级后GC 行为变了必须进行性能对比测试。开启 GC 日志这是最重要的调优依据。在启动参数中添加-Xlog:gc*,gcheapdebug,gcagetrace:filegc_%p_%t.log:time,uptime,level,tags:filecount10,filesize100M这个参数在 JDK 9 的 Unified Logging 框架下会输出非常详细的 GC 日志。filecount和filesize用于日志滚动防止磁盘写满。进行压力测试使用 JMeter、wrk 或生产类似的流量对升级前后的应用进行压测。记录吞吐量QPS/TPS平均响应时间、P95/P99 响应时间JVM 的 CPU 和内存使用情况分析 GC 日志关注以下关键指标Young GC 频率与耗时G1 的年轻代回收是否过于频繁平均耗时多少Mixed GC 情况G1 是否启动了混合回收回收了哪些老年代 RegionFull GC出现 Full GC 是警报G1 的设计目标之一就是避免 Full GC。如果出现说明配置可能不合理或存在内存问题。停顿时间对比 CMS 时代的停顿时间G1 是否满足-XX:MaxGCPauseMillis的目标默认 200ms3.4 G1 调优参数建议从 CMS 迁移而来如果默认 G1 表现不佳可以尝试以下调优但记住调优的原则是“先测量后调优”。调优目标关键 JVM 参数说明与建议控制停顿时间-XX:MaxGCPauseMillis200G1 的目标停顿时间。设为应用可接受的范围如 100-200ms。设得太小会导致 GC 更频繁反而降低吞吐。设置堆大小-Xms4g -Xmx4g建议设为相同值避免堆动态调整带来的额外 GC。G1 适合大堆建议至少 4GB 以上。调整并行线程数-XX:ParallelGCThreadsn并行阶段STW阶段的GC线程数。默认为CPU核数。如果GC线程占用CPU过多可适当调小。调整并发线程数-XX:ConcGCThreadsn并发阶段标记阶段的GC线程数。默认为ParallelGCThreads / 4。增加此值可加快并发标记但会占用更多应用CPU。调整 Region 大小-XX:G1HeapRegionSizenRegion 大小可为 1M 到 32M必须是2的幂。堆内存很大时如32G可考虑设置为 16M 或 32M。通常不需要改。触发混合回收的阈值-XX:InitiatingHeapOccupancyPercent45堆占用率达到多少时启动并发标记周期。默认45%。如果老年代增长快可以适当降低此值让 G1 更早开始标记。处理大对象-XX:G1MixedGCLiveThresholdPercent85-XX:G1HeapWastePercent5控制混合回收中哪些老年代Region会被回收。如果大对象多可以调整这些参数。一个从 CMS 迁移过来的示例启动参数# 原 CMS 参数JDK 8 # java -Xms4g -Xmx4g -XX:UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction75 -XX:UseCMSInitiatingOccupancyOnly -jar app.jar # 迁移后的 G1 参数JDK 17 java -Xms4g -Xmx4g \ -XX:UseG1GC \ -XX:MaxGCPauseMillis200 \ -XX:InitiatingHeapOccupancyPercent40 \ -Xlog:gc*,gcheapdebug:filegc.log:time,uptime,level,tags:filecount5,filesize50M \ -jar app.jar4. 何时考虑 ZGC 或 Shenandoah如果你的应用满足以下特征可以考虑跳过 G1直接评估 ZGC堆内存非常大超过 32GB甚至达到数百 GB。对停顿时间极度敏感要求 P99 响应时间稳定且停顿不能超过 10-20ms例如金融支付、实时竞价、游戏服务器。愿意付出额外资源ZGC 在并发阶段会消耗更多的 CPU 和内存带宽并且内存占用特别是堆外内存会比 G1 高。启用 ZGC 非常简单java -Xms32g -Xmx32g -XX:UseZGC -jar your-app.jar对于 ZGC通常只需要设置堆大小和启用开关。其核心优势就是“自动”试图减少调优参数。但务必在测试环境充分压测观察其 CPU 和内存开销是否符合预期。5. 常见问题与排查方法升级过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案应用启动失败报Unrecognized VM option UseConcMarkSweepGCJDK 17 中已移除 CMS 相关参数检查启动脚本和配置文件的 JVM 参数彻底删除所有-XX:UseConcMarkSweepGC、-XX:UseParNewGC等参数升级后Full GC 频繁或停顿时间变长1. G1 自适应调整期2. 堆大小或 Region 设置不合理3. 对象分配速率过快分析 GC 日志观察 Young/Mixed GC 频率和耗时使用jstat -gcutil监控1. 给予 JVM 预热时间如30分钟2. 调整-XX:MaxGCPauseMillis或-XX:InitiatingHeapOccupancyPercent3. 检查代码是否存在内存泄漏或过度分配启用 ZGC 后出现OutOfMemoryError: Metadata space或 Native Memory 增长ZGC 在 JDK 17 中内存占用较高且不支持类卸载使用 NMT 监控 Native Memory-XX:NativeMemoryTrackingdetail1. 增加 Metaspace 大小-XX:MaxMetaspaceSize512m2. 如果类加载频繁考虑升级到JDK 21ZGC 支持类卸载或暂用 G1压测时吞吐量下降明显GC 线程占用过多 CPU或目标停顿时间设置过小监控系统 CPU 使用率区分应用线程和 GC 线程1. 适当调整-XX:ParallelGCThreads或-XX:ConcGCThreads2. 放宽-XX:MaxGCPauseMillis年轻代 GC 异常频繁Eden 区过小或对象过早晋升分析 GC 日志中 Young GC 间隔和晋升大小1. 无需直接设置年轻代大小G1 会自动调整2. 检查代码中是否存在大量短命大对象或不当的缓存6. 最佳实践与使用建议优先使用默认值无论是 G1 还是 ZGC现代 JVM 的默认配置都经过了广泛测试。不要一上来就调参。先使用默认配置进行压测根据 GC 日志再决定是否需要调优。理解监控指标学会使用jstat、jcmd、GC 日志以及 APM 工具如 Prometheus Grafana监控 GC 行为。关键指标GC 频率、各阶段耗时、STW 总时间、内存使用趋势。堆大小设置黄金法则-Xms和-Xmx设置为相同值。避免堆自动扩容收缩带来的性能抖动。面向低延迟与高吞吐的抉择追求吞吐量可以继续使用-XX:UseParallelGCParallel Scavenge Parallel Old它在 JDK 17 中依然可用。追求低延迟默认的 G1 是安全且平衡的选择。堆超大且追求极致延迟再考虑 ZGC。升级路径对于复杂核心应用建议分阶段升级JDK 8 - JDK 11 (LTS) - JDK 17 (LTS)。JDK 11 是一个重要的中间版本许多内部 API 和 GC 的变更已发生在此版本验证兼容性更稳妥。测试环境充分验证升级必须在模拟生产环境的测试集群进行长时间至少数天的压测和稳定性测试观察高峰、平峰期的 GC 表现。从 JDK 8 升级到 JDK 17垃圾回收器的切换是技术升级的核心环节。放弃熟悉的 CMS拥抱 G1 或探索 ZGC需要理念上的转变从“手动精细调优”到“相信 JVM 的自动化与智能化”。掌握这五大回收器的工作原理能让你在升级时心中有图调优时手下有度。建议从默认的 G1 开始打开详细的 GC 日志让数据驱动你的决策这才是应对升级挑战最可靠的方法。
返回列表