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

资讯详情

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

JVM八大垃圾回收器全解析:从Serial到ZGC的选型与调优实战

JVM八大垃圾回收器全解析:从Serial到ZGC的选型与调优实战 1. 项目概述为什么我们需要八种不同的“清洁工”如果你写过Java程序大概率见过OutOfMemoryError这个老朋友。它就像一个不请自来的客人总是在你最不希望的时候出现。背后的问题往往指向JVM的垃圾回收机制。很多人觉得GCGarbage Collection是个黑盒参数调来调去效果时好时坏最后只能归结于“玄学”。但事实是JVM为我们提供了八种不同的垃圾回收器它们不是随意堆砌的选项而是针对不同时代、不同硬件、不同业务场景的精密工程解决方案。理解它们是告别内存问题“玄学”走向性能调优“科学”的第一步。这八位“清洁工”分别是Serial、Serial Old、ParNew、Parallel Scavenge、Parallel Old、CMS、G1、ZGC以及Shenandoah但本文聚焦主流八种。从单线程的“扫地僧”Serial到追求极致低延迟的“时间魔术师”ZGC每一种回收器的设计哲学、适用场景和内部运作机制都截然不同。搞懂它们意味着你能在项目初期就做出合理的架构选型在线上问题爆发时能快速定位瓶颈在面试中被问到“为什么我们系统用G1而不用CMS”时能给出有深度的回答而不是一句“因为大家都这么用”。简单来说这篇文章的目标就是帮你把这八种回收器从一堆陌生的名字变成你工具箱里可以随手取用、心中有数的利器。我们会从最基础的“为什么要分代回收”开始拆解每种回收器的核心工作原理、适用场景、关键参数和那些官方文档里不会写的“实战避坑指南”。无论你是正在被频繁Full GC困扰的开发者还是希望深入理解JVM的进阶学习者这篇文章都将提供一条清晰的路径。2. JVM垃圾回收基础与分代模型在深入每个回收器之前我们必须建立统一的“战场地图”——JVM的内存布局和分代收集理论。这是理解所有后续差异的基石。2.1 堆内存结构新生代、老年代与元空间现代JVM的堆内存Heap主要分为新生代Young Generation和老年代Old Generation。这不是物理分割而是逻辑上的划分基于一个核心观察绝大多数对象的生命周期都非常短暂“朝生夕死”。新生代新创建的对象首先被分配在这里。新生代内部又分为一个Eden区和两个Survivor区通常称为S0和S1或From和To。对象在Eden区诞生经过一次Minor GC后存活的对象会被移动到其中一个Survivor区。下次Minor GC时Eden区和当前使用的Survivor区比如From区中存活的对象会被复制到另一个空的Survivor区To区同时年龄加1。这个过程称为“复制-清除”。当对象的年龄经过Minor GC的次数超过一定阈值默认15它就会被晋升Promote到老年代。老年代存放生命周期较长的对象以及一些大对象可能直接分配在老年代取决于回收器实现和参数。老年代的空间通常远大于新生代。发生在老年代的GC称为Major GC或Full GCFull GC通常也会清理元空间其速度比Minor GC慢得多。元空间在JDK 8及以后永久代被元空间取代。它主要存储类的元数据、方法信息、常量池等。元空间使用本地内存因此理论上只受操作系统可用内存限制减少了因PermGen空间不足导致的OutOfMemoryError。分代收集的核心思想是针对不同生命周期的对象采用不同的回收策略从而以较低的代价回收大部分内存。2.2 评判垃圾回收器的核心指标选择回收器就是做权衡。你需要根据你的业务特点决定优先保障哪个指标吞吐量单位时间内应用程序执行用户代码的时间占总运行时间的比例。公式为吞吐量 用户代码运行时间 / (用户代码运行时间 GC时间)。例如99%的吞吐量意味着GC时间只占1%。这对后台计算、科学运算等任务至关重要。延迟单次GC事件导致的应用程序停顿时间。也称为“停顿时间”或“响应时间”。对于Web服务、交易系统等需要与用户交互的应用较长的停顿会导致请求超时、用户体验下降。内存占用垃圾回收器本身运行所需的内存开销。例如一些并发回收器需要额外的空间用于标记等操作。内存碎片频繁的分配和回收会在堆中产生大量不连续的小块空闲内存虽然总空闲内存可能足够但无法分配一个大对象从而触发不必要的Full GC。没有任何一个回收器能在所有指标上都做到最优。Serial追求极简低开销Parallel追求高吞吐CMS和G1尝试降低延迟ZGC和Shenandoah则追求极致的低延迟。注意不要盲目追求“零停顿”。极低延迟的回收器通常在吞吐量或内存占用上有所妥协。你的选择应该基于可量化的业务指标比如“95%的请求响应时间必须低于200ms”然后选择能帮助达成该目标的回收器并合理配置。3. 串行时代Serial与Serial Old回收器我们把Serial和Serial Old放在一起讲因为它们是JVM最古老、最简单的垃圾回收器通常搭配使用。3.1 设计哲学与工作原理Serial收集器是一个单线程的收集器。它的“单线程”意义不仅仅是使用一个CPU或一条线程进行垃圾回收更重要的是在进行垃圾回收时它必须暂停所有其他工作线程直到回收结束。这种行为被称为“Stop-The-World”。想象一下整个城市只有一个清洁工他工作时所有市民必须回家等待街道空无一人。Serial用于新生代的垃圾回收采用复制算法。它将Eden区和一个Survivor区中存活的对象复制到另一个Survivor区然后直接清空Eden和之前的Survivor区。Serial Old用于老年代的垃圾回收采用标记-整理算法。它首先标记出所有存活的对象然后将这些对象向内存空间的一端移动最后直接清理掉边界以外的内存。3.2 适用场景与参数配置在今天动辄多核服务器的环境下Serial收集器听起来已经过时。但它依然有其不可替代的价值客户端模式或微型应用对于客户端GUI程序如Swing应用或非常小的、不关心停顿的批处理任务Serial收集器简单高效没有线程交互的开销额外内存消耗最小。资源极度受限的环境例如在一些嵌入式系统或早期移动设备上CPU核心数少内存小Serial是唯一或最合适的选择。JVM安全点机制的后备在某些极端情况下其他收集器可能会退回到Serial Old来执行Full GC。在服务端模式下默认不会使用Serial收集器。如果需要显式指定可以使用以下JVM参数-XX:UseSerialGC这个参数会同时指定新生代使用Serial老年代使用Serial Old。3.3 实战心得与局限性心得在本地开发、单元测试环境中使用-XX:UseSerialGC有时是个好主意。因为它简单日志清晰能让你更直观地观察GC行为排除复杂并发回收器带来的干扰快速定位是否是内存泄漏等根本性问题。局限性是显而易见的STW时间与堆内存大小、存活对象数量直接相关。对于现代动辄数GB甚至数十GB的堆内存Serial收集器的停顿时间可能达到数秒甚至数十秒这对于任何在线服务都是不可接受的。因此它彻底退出了服务端主流应用的舞台。4. 吞吐量优先Parallel Scavenge与Parallel Old回收器这是JDK 8及之前版本的默认垃圾回收器在服务端模式下。它的设计目标是最大化应用程序的吞吐量。4.1 并行回收的核心思想Parallel Scavenge新生代和Parallel Old老年代都是多线程并行的收集器。在GC时它们会使用多个线程同时进行垃圾回收工作但仍然需要Stop-The-World。与Serial的单线程“清洁工”相比它像是一支清洁队同时进场工作虽然市民仍需等待但等待时间大大缩短。Parallel Scavenge新生代收集器使用复制算法并行清理。Parallel Old老年代收集器使用标记-整理算法并行清理。它的核心优势在于高效利用多核CPU资源在最短的挂起时间内完成垃圾回收从而减少GC时间占总时间的比例提升吞吐量。4.2 关键调优参数详解Parallel收集器提供了一系列以-XX:MaxGCPauseMillis和-XX:GCTimeRatio为核心的目标调优参数这是它与后续并发收集器一个重要的设计哲学区别它允许你设定目标JVM会尝试动态调整堆大小等参数来达到目标。-XX:UseParallelGC/-XX:UseParallelOldGC-XX:UseParallelGC新生代使用Parallel Scavenge老年代使用Serial Old单线程。-XX:UseParallelOldGC新生代使用Parallel Scavenge老年代使用Parallel Old。这是开启吞吐量优先模式的推荐方式。-XX:ParallelGCThreads设置并行GC时的线程数。默认值通常与CPU核心数相关。在CPU密集型应用中如果GC线程过多可能会与应用线程争抢CPU反而降低吞吐量。一般不建议手动设置除非有明确证据表明默认值不合适。-XX:MaxGCPauseMillis期望的最大GC停顿时间毫秒。这是一个软目标JVM会尽力实现但不保证。设置需谨慎如果你将其设得过小如10msJVM为了达到这个目标可能会频繁进行GC或者大幅缩小堆空间导致总体吞吐量急剧下降。通常建议设置一个合理的值如100-200ms然后观察实际效果。-XX:GCTimeRatio吞吐量目标。公式为1 / (1 GCTimeRatio)。默认值99表示GC时间占总时间的1/(199)1%即吞吐量目标为99%。增大此值会提高吞吐量目标。-XX:UseAdaptiveSizePolicy这是一个非常重要的参数默认开启。开启后JVM会根据运行时性能数据动态调整新生代大小、Eden与Survivor比例、对象晋升老年代年龄等参数以试图达到MaxGCPauseMillis或GCTimeRatio设定的目标。对于大多数情况建议保持开启让JVM自行优化。4.3 生产环境配置案例与避坑指南假设我们有一个后台数据处理服务对延迟不敏感但要求尽可能高的吞吐量。机器配置为8核16G。java -Xmx8g -Xms8g \ -XX:UseParallelOldGC \ -XX:MaxGCPauseMillis200 \ -XX:GCTimeRatio99 \ -XX:PrintGCDetails \ -XX:PrintGCDateStamps \ -Xloggc:/path/to/gc.log \ -jar your-application.jar-Xmx8g -Xms8g将堆初始和最大值设为相同避免运行时动态调整带来的性能波动。-XX:UseParallelOldGC启用并行回收。-XX:MaxGCPauseMillis200设定200ms的停顿目标相对宽松。-XX:GCTimeRatio99吞吐量目标99%。后面是GC日志参数用于监控和分析。避坑指南不要迷信MaxGCPauseMillis它只是一个优化指引不是硬性承诺。将其设得过小是Parallel收集器调优中最常见的错误会导致吞吐量暴跌。务必通过GC日志监控实际停顿时间。关注“晋升失败”如果老年代空间不足无法容纳从新生代晋升上来的对象会触发一次昂贵的Full GC。确保老年代大小足够。可以通过-XX:PretenureSizeThreshold设置大对象直接进入老年代的阈值避免大对象在新生代反复拷贝。自适应策略的“黑盒”效应UseAdaptiveSizePolicy虽然方便但也使得GC行为变得不那么直观。如果出现性能问题可以尝试暂时关闭它-XX:-UseAdaptiveSizePolicy手动设置-Xmn新生代大小、-XX:SurvivorRatio等参数进行对比测试以确定根本原因。5. 响应时间优先ParNew与CMS回收器当互联网应用兴起用户对响应时间的要求越来越高时Parallel收集器较长的STW停顿就成了问题。CMS收集器应运而生它的目标是减少垃圾回收尤其是老年代回收的停顿时间。ParNew是它的“专属搭档”。5.1 CMS的并发低延迟设计CMS的全程是Concurrent Mark-Sweep即并发标记清除。它的革命性在于将老年代GC中最耗时的标记阶段做到了与用户线程并发执行从而极大地减少了STW时间。CMS收集过程分为四个核心阶段初始标记STW。速度极快仅标记GC Roots能直接关联到的对象。并发标记并发执行。从初始标记的对象开始进行可达性分析标记所有存活对象。这个阶段耗时最长但与应用线程一起运行。重新标记STW。修正并发标记期间因用户线程继续运行而导致标记产生变动的那些对象的标记记录。这个阶段停顿时间通常比初始标记长但远短于并发标记的总体时间。并发清除并发执行。清理死亡对象。可以看到CMS只在初始标记和重新标记两个阶段需要短暂的STW。ParNew收集器是CMS在新生代的标配它本质上是Serial收集器的多线程并行版本采用复制算法需要STW。5.2 核心参数与典型配置启用CMS的组合参数是-XX:UseParNewGC -XX:UseConcMarkSweepGC或者简写为-XX:UseConcMarkSweepGC在JDK 9之前这会自动启用ParNew作为新生代收集器。关键调优参数-XX:CMSInitiatingOccupancyFraction这是最重要的参数。它设定老年代空间使用率达到多少百分比时触发CMS收集。默认值是-1会由JVM自动计算一个值约68%。你必须显式设置这个值如果设置过高如90%可能在CMS并发收集还没完成时老年代就满了此时JVM会触发一次“并发模式失败”后备方案是启动Serial Old收集器进行Full GC导致一次长时间的STW。建议设置为70-75%为并发收集预留足够时间。-XX:UseCMSInitiatingOccupancyOnly与上一个参数配套使用。表示只使用CMSInitiatingOccupancyFraction的值作为触发条件禁止JVM自行调整。建议总是启用。-XX:CMSFullGCsBeforeCompaction设置执行多少次不压缩的Full GC后跟着来一次带压缩的Full GC。因为CMS是“标记-清除”算法会产生内存碎片。此参数默认为0表示每次Full GC都进行压缩整理。碎片问题严重时可以调整。-XX:CMSScavengeBeforeRemark强烈建议开启。在重新标记阶段之前先对新生代进行一次Minor GC。这样可以减少新生代对象对老年代的引用缩小重新标记阶段需要扫描的范围有效缩短重新标记的STW时间。-XX:ParallelCMSThreads设置CMS并发标记和清除的线程数。默认值基于并行GC线程数计算。一个典型的生产环境CMS配置可能如下java -Xmx4g -Xms4g \ -XX:UseConcMarkSweepGC \ -XX:CMSInitiatingOccupancyFraction75 \ -XX:UseCMSInitiatingOccupancyOnly \ -XX:CMSScavengeBeforeRemark \ -XX:ExplicitGCInvokesConcurrent \ -jar your-webapp.jar-XX:ExplicitGCInvokesConcurrent让System.gc()调用触发CMS并发收集而不是Full GC避免代码中的显式GC调用导致长时间停顿。5.3 CMS的经典问题与应对策略CMS是划时代的产物但它并非完美其缺点非常鲜明内存碎片“标记-清除”算法不进行压缩长期运行后会产生大量内存碎片。当需要分配一个大对象时即使总空闲内存足够也可能因找不到连续空间而触发Full GC。应对策略合理设置-XX:CMSFullGCsBeforeCompaction或者监控到碎片严重时在业务低峰期手动触发一次Full GC。并发模式失败在并发清理阶段老年代空间仍在被使用。如果此时用户线程需要分配一个大对象或者老年代空间增长过快导致在老年代被填满前CMS还没完成清理就会发生“并发模式失败”触发Serial Old进行Full GC。这是CMS最需要避免的情况。应对策略务必合理设置-XX:CMSInitiatingOccupancyFraction留足余量增大老年代空间优化程序减少大对象分配或对象晋升速度。CPU资源敏感并发标记和清理阶段GC线程会与应用线程竞争CPU资源可能导致应用程序在GC期间的吞吐量下降。在CPU核心数少的机器上尤其明显。浮动垃圾并发清理阶段用户线程还在运行会产生新的垃圾对象这部分垃圾只能等到下次GC才能被清理。由于这些固有缺陷以及后续G1等更优秀回收器的成熟CMS在JDK 9中被标记为废弃并在JDK 14中被彻底移除。但对于一些运行在旧JDK版本上、堆内存不大如8G以下、对延迟敏感的应用理解CMS仍有其历史价值和现实意义。6. 里程碑G1垃圾回收器G1是JDK 9及以后版本的默认垃圾回收器它的设计目标是在可预测的停顿时间模型下实现高吞吐量。它试图取代CMS并解决了CMS的内存碎片等问题。6.1 区域化与记忆集G1抛弃了连续的新生代、老年代物理划分将整个堆划分为多个大小相等默认约2048个Region每个Region大小1M-32M的独立区域。每个Region都可以扮演Eden、Survivor、Old、Humongous巨型对象区域角色。这种划分是G1实现可预测停顿的基础。G1的回收不再是整代回收而是优先回收价值最大的Region即垃圾最多的RegionGarbage First名字的由来。它通过维护一个记忆集来记录Region之间的对象引用关系。当进行跨Region的引用扫描时无需扫描整个堆只需查询记忆集这大大减少了扫描范围。6.2 G1的运作周期Young GC与Mixed GCG1的GC活动分为两种Young GC当Eden区Region被填满时触发。这是一个STW的并行复制过程将Eden区和Survivor区存活的对象复制到新的Survivor区或老年代Region。这个过程与Parallel Scavenge类似但只涉及部分Region。Mixed GC这是G1的核心。当老年代Region的占用比例超过阈值-XX:InitiatingHeapOccupancyPercent默认45%时触发。Mixed GC不仅会回收所有新生代Region还会根据“价值”计算选择一部分老年代Region进行回收。它分为以下几个阶段其中全局并发标记与用户线程并发执行初始标记STW标记GC Roots直接关联的对象。并发标记并发执行标记整个堆的存活对象。最终标记STW处理并发标记阶段留下的少量SATBSnapshot-At-The-Beginning记录。筛选回收STW根据停顿时间目标选择价值最高的Region组成回收集将其中的存活对象复制到空的Region然后清空整个回收集。这个阶段是并行执行的。6.3 关键参数调优与实践启用G1非常简单-XX:UseG1GC核心调优参数-XX:MaxGCPauseMillis期望的最大停顿时间目标。默认200ms。G1会尽力达到这个目标但不保证。与Parallel收集器不同G1会通过调整每次回收的Region数量回收集的大小来动态控制停顿时间。-XX:G1HeapRegionSize设置Region大小。范围1M-32M必须是2的幂。通常不需要手动设置G1会根据堆大小自动计算。如果应用有大量大对象可以适当调大此值减少Humongous Region的数量。-XX:InitiatingHeapOccupancyPercent触发Mixed GC的堆占用阈值。默认45%。如果Mixed GC发生频繁可以适当调高如果发生过早导致老年代增长过快可以适当调低。-XX:G1ReservePercent设置堆内存的保留空间比例用于在复制存活对象时备用防止晋升失败。默认10%。在对象晋升非常频繁的应用中可以适当增加。-XX:G1PrintRegionLivenessInfo/-XX:PrintAdaptiveSizePolicy诊断参数用于在日志中打印Region的详细信息或自适应策略的决策过程帮助深度调优。生产环境配置示例java -Xmx16g -Xms16g \ -XX:UseG1GC \ -XX:MaxGCPauseMillis100 \ -XX:InitiatingHeapOccupancyPercent40 \ -XX:PrintGCDetails \ -XX:PrintGCDateStamps \ -XX:PrintGCTimeStamps \ -XX:PrintAdaptiveSizePolicy \ -jar your-microservice.jarG1调优心得关注“Evacuation Failure”当复制存活对象时没有足够的空闲Region包括预留空间就会发生“疏散失败”这会触发一次Full GCSerial Old。这是G1最严重的失败场景。通常是因为-XX:MaxGCPauseMillis设置过小导致每次回收的Region太少垃圾产生速度大于回收速度。首要任务是合理设置停顿时间目标不要不切实际地追求极低停顿。巨型对象处理大于Region一半的对象会被视为巨型对象存放在Humongous Region。这些Region的回收只在Full GC或并发标记周期结束时处理。如果应用产生大量巨型对象会对G1性能产生较大影响。考虑优化程序拆分大对象。监控是关键使用jstat -gcutil、GC日志分析工具或APM工具密切关注Mixed GC的频率、耗时以及老年代使用率的增长曲线。健康的G1应该能通过定期的Mixed GC将老年代使用率控制在一个平稳的范围内避免Full GC。7. 前沿科技ZGC与Shenandoah当G1将停顿时间目标控制在200ms左右时新一代的回收器ZGC和Shenandoah则将目标指向了亚毫秒级停顿即使面对TB级别的堆内存。7.1 ZGC基于染色指针的低延迟王者ZGC在JDK 15中成为生产就绪的特性。它的核心技术是染色指针和读屏障。染色指针ZGC将GC相关的元数据信息如标记、重定位状态存储在对象指针本身的几位比特位上而不是像传统回收器那样存储在独立的数据结构中。这带来了一个巨大优势对象在堆内的位置可以任意移动而指向它的引用只需要修改指针中的地址部分其元数据部分保持不变。这使得对象压缩解决碎片问题变得异常廉价。读屏障在应用程序线程从堆中加载对象引用时会插入一小段代码读屏障用于检查指针的元数据状态并在必要时触发一些协同操作如对象的重定位。这个操作开销被设计得非常小。ZGC的工作周期也是并发标记、并发转移预备、并发转移、并发重定位等但几乎所有耗时操作都是并发的STW阶段只存在于根节点枚举而这个阶段耗时极短通常不超过1-2毫秒且与堆大小无关。7.2 Shenandoah与ZGC并驾齐驱Shenandoah由Red Hat开发目标与ZGC类似但实现技术不同。它使用Brooks指针来实现并发压缩。每个对象都有一个额外的转发指针。当对象被移动时在原位置留下一个“转发指针”指向新地址。访问对象时通过读屏障来解析这个转发指针。与ZGC相比Shenandoah的读屏障开销略高但其算法在某些场景下可能更简单。两者都是业界顶尖的低延迟回收器。7.3 适用场景与启用方式适用场景堆内存巨大数十GB至TB级别。对停顿时间有极端要求如金融交易系统、实时游戏服务器、大型分布式缓存等。应用本身吞吐量可以接受一定程度的牺牲通常低于G1/Parallel几个百分点。启用方式ZGC:-XX:UseZGC。在JDK 11中作为实验特性引入JDK 15后生产可用。通常需要搭配-XX:UseLargePages大页内存以获得最佳性能。Shenandoah:-XX:UseShenandoahGC。在OpenJDK中提供但并非所有JDK发行版都包含如Oracle JDK不包含。一个简单的ZGC配置java -Xmx32g -Xms32g \ -XX:UseZGC \ -XX:UseLargePages \ -XX:PrintGCDetails \ -XX:PrintGCTimeStamps \ -jar your-low-latency-app.jar重要提醒ZGC和Shenandoah是面向未来的回收器它们正在快速发展中。在生产环境采用前务必在你的具体应用和硬件环境下进行充分的压测和稳定性测试。对于大多数中小型应用G1可能仍然是更成熟、更稳妥的选择。8. 回收器选型与调优实战指南了解了所有回收器后面对一个具体项目我们该如何选择又该如何开始调优8.1 如何根据业务场景选择回收器可以遵循以下决策树堆内存很小100M或客户端应用优先考虑Serial GC。简单稳定开销最小。追求高吞吐量对停顿不敏感例如后台报表生成、批处理任务。优先选择Parallel GC。堆内存中等4G-8G追求较低停顿如果应用运行在JDK 8或更早版本CMS是一个选项但需准备好应对碎片和并发失败问题。更推荐升级JDK并使用G1。堆内存较大8G需要可控的停顿时间G1是默认和推荐的选择。它在吞吐量和延迟之间取得了很好的平衡。堆内存巨大32G要求极低停顿10ms考虑ZGC或Shenandoah。进行严格的测试。简单口诀小或客户端用Serial要吞吐用Parallel要平衡用G1要极限低延迟用ZGC/Shenandoah。CMS是JDK 8时代的遗产新项目应避免使用。8.2 通用调优步骤与监控方法调优不是一蹴而就的是一个“监控-分析-调整-验证”的循环。第一步建立监控基线使用-Xlog:gc*:filegc.logJDK 9或-XX:PrintGCDetails -Xloggc:gc.logJDK 8输出详细的GC日志。使用jstat -gcutil pid 1s实时观察各代使用率和GC次数/时间。使用APM工具如Prometheus Grafana JMX Exporter对GC时间、频率、堆内存使用情况进行可视化监控和告警。第二步分析关键指标吞吐量1 - (总GC时间 / 总运行时间)。目标通常95%。延迟关注最大停顿时间和百分位停顿时间如P99P999。使用GC日志分析工具如GCeasy, G1Analyzer或APM工具获取。内存观察老年代使用率增长是否平稳Full GC频率是否异常。第三步针对性调整频繁Full GC检查是否是晋升失败、并发模式失败、内存碎片导致。调整晋升阈值、触发阈值或考虑切换到G1。年轻代GC频繁适当增加新生代大小-Xmn或G1的-XX:G1NewSizePercent但注意这会缩小老年代。单次GC停顿过长对于Parallel检查是否MaxGCPauseMillis设得不合理对于G1尝试稍微放宽MaxGCPauseMillis目标对于CMS检查重新标记时间开启-XX:CMSScavengeBeforeRemark。吞吐量不达标尝试使用Parallel收集器或者为G1/ZGC提供更多的CPU资源。第四步压测验证任何参数修改都必须通过模拟生产流量的压测来验证效果。对比调优前后的监控图表和性能指标。8.3 常见问题排查清单当你收到告警或用户反馈“系统变慢”时可以按此清单快速排查GC问题现象可能原因排查命令/日志关键词初步应对思路CPU持续高占用频繁GCGC线程持续工作top -Hp pid查看GC线程CPUjstat -gcutil查看GC频率分析GC日志看是哪种GC频繁。可能是内存分配过快或泄漏。服务响应时间周期性变长发生长时间的Full GCGC日志中出现 “Full GC” 或 “Pause Full”分析Full GC原因Allocation Failure,Metadata GC Threshold,Ergonomics等。老年代使用率持续增长不下降内存泄漏或大对象/缓存无法回收jmap -histo:live pid查看对象直方图jmap -dump生成堆转储后用MAT分析查找疑似泄漏的对象类。检查缓存策略。CMS并发模式失败CMS回收速度跟不上对象分配速度GC日志中出现 “concurrent mode failure”降低CMSInitiatingOccupancyFraction增加堆大小优化代码减少对象分配。G1疏散失败没有足够Region容纳复制对象GC日志中出现 “Evacuation Failure”增加-XX:G1ReservePercent放宽-XX:MaxGCPauseMillis检查是否有大量巨型对象。年轻代GC时间过长存活对象过多复制开销大观察jstat中YGC时间检查Survivor区是否过小导致对象过早晋升检查是否有大量“朝生夕死”的大对象。最后记住一句经验之谈调优的终极手段是优化应用程序代码本身。减少不必要的对象创建、避免内存泄漏、使用合理的缓存规模和过期策略、选择更高效的数据结构这些代码层面的优化往往比在JVM参数上绞尽脑汁带来的收益要大一个数量级。JVM垃圾回收器是你强大的盟友但写出高效、整洁的代码才是解决问题的根本。
返回列表