单次 CMS Old GC 耗时长问题分析与优化

目录

一、现象说明

二、CMS GC 机制简述

三、可能导致长时间停顿的原因详细分析

(一)Full GC(完全垃圾回收)

1. 主要原因

2.参数调整

(二)Promotion Failure(晋升失败)

1. 主要原因

2. 参数调整

(三)内存碎片化问题

1. 主要原因

2. 参数调整

(四)并发标记和清理阶段的问题

1. 主要原因

2. 参数调整

(五)GC 并行度不足

1. 主要原因

2. 参数调整

四、优化思路与方案

(一)增加 Old Generation 的内存

1. 背景与原因

2. 具体方案

(二)减少 Promotion Failure

1. 背景与原因

2. 具体方案

(三)减少内存碎片

1. 背景与原因

2. 具体方案

(四)增加 CMS 的并行度

1. 背景与原因

2. 具体方案

(五)切换到 G1 GC

1. 背景与原因

2. 具体方案

(六)使用 ZGC 或 Shenandoah GC

1. 背景与原因

2. 具体方案

五、总结


干货分享,感谢您的阅读!

在高并发的生产环境中,Java 应用通常依赖于垃圾回收机制(GC)来管理内存。CMS(Concurrent Mark-Sweep)垃圾回收器是一种较为常见的垃圾回收策略,它通过并行回收来降低停顿时间。然而,在某些情况下,CMS 的 Old GC(老年代回收)阶段会出现较长的停顿,甚至超过 1000ms,极端情况下可能达到 8000ms。这类长时间的 GC 停顿会严重影响系统的响应时间,并可能引发系统的雪崩效应,即多个服务由于 GC 停顿相互影响,导致整个系统性能下降。

历史主要基本文章回顾:

涉猎内容 具体链接
Java GC 基础知识快速回顾 Java GC 基础知识快速回顾-CSDN博客
垃圾回收基本知识内容 Java回收垃圾的基本过程与常用算法_java垃圾回收过程-CSDN博客
CMS调优和案例分析 CMS垃圾回收器介绍与优化分析案列整理总结_cms 对老年代的回收做了哪些优化设计-CSDN博客
G1调优分析 Java Hotspot G1 GC的理解总结_java g1-CSDN博客
ZGC基础和调优案例分析 垃圾回收器ZGC应用分析总结-CSDN博客

从ES的JVM配置起步思考JVM常见参数优化

从ES的JVM配置起步思考JVM常见参数优化_es jvm配置-CSDN博客

深入剖析GC问题:如何有效判断与排查

深入剖析GC问题:如何有效判断与排查_排查java堆中大对象触发gc-CSDN博客

动态扩缩容引发的JVM堆内存震荡调优指南

动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南

显式 GC 的使用:留与去,如何选择?

显式 GC 的使用:留与去,如何选择?

过早晋升的识别与优化实战

Java垃圾回收的隐性杀手:过早晋升的识别与优化实战

如何选取合适的 NewRatio 值

如何选取合适的 NewRatio 值来优化 JVM 的垃圾回收策略

解决 CMS Old GC 频繁触

解决 CMS Old GC 频繁触发优化 Java 性能的技术方案

高效解决MetaSpace OOM 问题

深入剖析 MetaSpace OOM 问题:根因分析与高效解决策略
高频面试题汇总 JVM高频基本面试问题整理_jvm面试题-CSDN博客

一、现象说明

在一些高并发的生产环境中,GC 停顿时间的增加会直接影响应用的可用性。特别是在使用 CMS 回收器时,Old Generation 的垃圾回收经常会导致长时间的 STW(Stop-the-World)停顿。STW 阶段是指 JVM 停止所有用户线程进行垃圾回收工作,这一阶段的停顿时间如果过长,将严重影响系统的响应时间。

影响分析

  • 响应时间:GC 停顿时间过长时,用户请求可能会被阻塞,导致响应延迟。
  • 雪崩效应:多个服务由于 GC 停顿时间过长而相互影响,导致系统性能整体下降,甚至出现多个服务挂掉的情况。

这种问题虽然不是频繁发生,但一旦发生,会显著降低用户体验,尤其在对延迟敏感的应用中更为严重。因此,准确分析和优化 CMS Old GC 的 STW 停顿时间,是确保系统稳定性和高可用性的关键。

二、CMS GC 机制简述

在了解问题根源之前,我们首先回顾一下 CMS 的工作机制。CMS 是一种低停顿的垃圾回收器,旨在减少 Stop-the-World 时间。

为了避免长时间Stop the World,CMS采用了4个阶段来垃圾回收,分别是初始标记、并发标记、重新标记和并发清理。其中初始标记和重新标记,耗时很短,虽然会导致Stop the World,但是影响不大,然后并发标记和并发清理,两个阶段耗时最长,但是是可以跟系统的工作线程并发运行的,所以对系统没太大影响。具体图示如下:

如果细化的话,它的回收过程分为以下几个主要阶段:

  1. 初始标记(Initial Mark):这个阶段会进行 Stop-the-World,标记根对象。该阶段相对较快。
  2. 并发标记(Concurrent Mark):在这个阶段,JVM 会在用户线程运行的同时,标记所有活动对象。这是 CMS 的核心特性,它能够避免长时间的停顿。
  3. 并发预清理(Concurrent Preclean):该阶段会清理一些不再需要的对象。
  4. 重新标记(Remark):在这个阶段,JVM 需要暂停所有应用线程,重新标记在并发标记期间被修改的对象。
  5. 并发清理(Concurrent Sweep):清理标记过的垃圾对象,并回收内存空间。
  6. 并发重生(Concurrent Reset):用于回收 Old Generation 空间的恢复。

然而,尽管 CMS 的设计理念是减少停顿时间,在一些高并发或内存压力较大的环境下,CMS 仍然可能出现长时间的 STW 停顿,特别是在 Old Generation 的回收阶段。

三、可能导致长时间停顿的原因详细分析

(一)Full GC(完全垃圾回收)

当 Old Generation 空间不足时,JVM 会触发 Full GC(完全垃圾回收)。在 CMS 回收器中,Full GC 会清理整个堆(包括 Young 和 Old Generation),从而导致长时间的 STW 停顿。特别是在 Old Generation 中有大量活跃对象时,Full GC 可能会清理大量对象,导致 GC 停顿时间显著增加。

1. 主要原因

当 Old Generation 的内存压力较大时,JVM 可能无法有效回收足够的内存,导致需要进行 Full GC。而 CMS 的设计目标是减少 STW 时间,但 Full GC 的过程仍然是 Stop-the-World 的,意味着会暂停所有应用线程。

2.参数调整

// 调整 Old Generation 的大小
-XX:OldGenSize=4g   // 设置 Old Generation 内存为 4GB

// 开启 Full GC 时的内存压缩
-XX:+UseCMSCompactAtFullCollection   // 在 Full GC 时进行内存压缩
  • 参数-XX:OldGenSize 用于指定 Old Generation 的大小。
  • 参数-XX:+UseCMSCompactAtFullCollection 用于在 Full GC 时进行内存压缩,减少碎片。

调优思路

  1. 增加 Old Generation 空间:通过增加 -XX:OldGenSize,可以降低频繁 Full GC 的概率。
  2. 分析 Full GC 日志:查看 Full GC 的日志,以了解是否是 Old Generation 空间不足导致了 Full GC。

如果日志中显示频繁发生 Full GC,并且 Old Generation 空间较小,可以通过增大 -XX:OldGenSize 来减少 Full GC 的触发频率。

[GC (Allocation Failure) [PSYoungGen: 8192K->832K(9216K)] 
[ParOldGen: 6656K->1510K(10240K)] 14848K->2332K(19456K), 0.0167736 secs]

在上面的 GC 日志中,Full GC 发生了,而 Old Generation 被清理了 6656K 的内存。如果此时 Old Generation 空间过小,可能会频繁触发 Full GC。

(二)Promotion Failure(晋升失败)

Promotion Failure 指的是在 CMS 回收过程中,如果 Young Generation 中的对象无法在足够的空间内晋升到 Old Generation,就会发生晋升失败。此时,JVM 会触发 Full GC,清理 Old Generation,以便腾出空间进行晋升。

1. 主要原因

CMS 在 Young Generation 回收时,若有大量对象需要晋升到 Old Generation,但 Old Generation 空间不足,则会发生 Promotion Failure,导致 GC 停顿时间增加。

2. 参数调整

// 控制晋升阈值,减少晋升失败
-XX:MaxTenuringThreshold=5   // 对象在 Young Generation 中存活 5 次后晋升

// 调整 Survivor 区域的比例
-XX:SurvivorRatio=8   // Survivor 区与 Eden 区的比例为 8:1
  • 参数-XX:MaxTenuringThreshold 用于控制晋升到 Old Generation 的阈值。
  • 参数-XX:SurvivorRatio 用于调整 Young Generation 的大小比例,从而影响对象的晋升。

调优思路

  1. 减少对象晋升到 Old Generation 的频率:通过调整 -XX:MaxTenuringThreshold 参数,减少大量对象被晋升到 Old Generation,降低晋升失败的风险。
  2. 增加 Young Generation 大小:通过调整 -XX:SurvivorRatio-XX:NewSize 参数,优化 Young Generation 的大小,使更多对象在 Young Generation 中被回收。

如果日志显示频繁的 Promotion Failure,可以通过调整 -XX:MaxTenuringThreshold,将对象晋升阈值调整为更大的数值,或者通过增大 Young Generation 空间来减少晋升失败。

(三)内存碎片化问题

CMS 使用标记-清理算法,清理后会产生内存碎片。如果 Old Generation 中存在大量碎片,JVM 在清理时可能无法回收足够的连续内存块,从而导致 GC 停顿时间的增加。

1. 主要原因

在清理阶段,CMS 会标记活动对象并清理未被标记的垃圾对象,但标记-清理算法的一个问题是可能产生大量碎片,导致 Old Generation 空间无法有效回收连续的内存块。

2. 参数调整

// 开启 Full GC 时的内存压缩
-XX:+UseCMSCompactAtFullCollection   // 在 Full GC 时进行内存压缩

// 控制堆的自由内存比例
-XX:MinHeapFreeRatio=40   // 最小空闲比例为 40%
-XX:MaxHeapFreeRatio=70   // 最大空闲比例为 70%
  • 参数-XX:+UseCMSCompactAtFullCollection 在 Full GC 时进行内存压缩,减少碎片。
  • 参数-XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio 用于控制堆的自由内存比例,避免过多的碎片。

调优思路

  1. 开启内存压缩:通过开启 -XX:+UseCMSCompactAtFullCollection,确保 Full GC 期间进行内存压缩,减少碎片。
  2. 调整堆内存比例:通过调整 -XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio 参数,减少碎片化的影响。

如果 Old Generation 中存在大量碎片,可以通过开启 -XX:+UseCMSCompactAtFullCollection 来减少 Full GC 时的碎片,或者通过调整堆内存比例来优化内存分配。

(四)并发标记和清理阶段的问题

CMS 的并发标记和清理阶段设计目的是在应用线程运行的同时,标记和清理不再使用的对象。然而,在高并发环境下,多个线程对内存的争用可能导致标记和清理过程变得低效,进而增加 STW 停顿时间。

1. 主要原因

在并发标记阶段,多个线程可能会争用资源,特别是在多核 CPU 环境中,CPU 核数较少时,标记和清理的并行度不足,可能导致整个 GC 过程变慢。

2. 参数调整

// 增加 CMS 的并行标记和清理线程数
-XX:ParallelCMSThreads=8   // 增加并发标记和清理的线程数
  • 参数-XX:ParallelCMSThreads 用于调整并发标记和清理阶段的线程数。

调优思路

  1. 增加并发线程数:通过增加 -XX:ParallelCMSThreads 参数,增加并发标记和清理的线程数,提升回收效率。
  2. 查看 GC 线程使用情况:通过监控 CPU 核数和线程利用率,确保多核资源得到充分利用。

(五)GC 并行度不足

如果系统的 CPU 核数较少,或者配置的 GC 并行度参数不足,CMS 的并行标记和清理阶段就无法充分并行化,导致 GC 效率低下,从而增加 STW 停顿时间。

1. 主要原因

当系统的 CPU 核数较少时,CMS 的并行度可能不足,无法充分利用多核资源,导致 GC 效率低下,从而增加停顿时间。

2. 参数调整

// 增加 GC 并行度,充分利用多核 CPU
-XX:ParallelGCThreads=8   // 设置并行 GC 线程数
  • 参数-XX:ParallelGCThreads 用于设置 GC 并行度。

调优思路

  1. 增加 GC 并行度:通过调整 -XX:ParallelGCThreads 参数,增加 GC 的并行度,提升回收效率。
  2. 合理分配 CPU 资源:确保系统的 CPU 资源能够充分用于 GC

四、优化思路与方案

(一)增加 Old Generation 的内存

1. 背景与原因

在 CMS 回收器中,Old Generation 是存储长期存在的对象的区域。如果 Old Generation 的空间不足,JVM 就会触发 Full GC。频繁的 Full GC 会导致长时间的 STW 停顿,进而影响系统的响应时间和吞吐量。因此,适当增加 Old Generation 的内存,可以有效避免频繁的 Full GC。

2. 具体方案

通过调整以下 JVM 参数,可以增加 Old Generation 的内存,并减少 Old GC 触发的频率:

// 增加 Old Generation 内存大小
-XX:OldGenSize=4g  // 设置 Old Generation 内存为 4GB

// 调整 Old Generation 与 Young Generation 的比例
-XX:NewRatio=3  // 增加 Old Generation 比例,Young Generation 占 1/4 堆内存
  • -XX:OldGenSize:用于指定 Old Generation 的大小。通过增大该参数,减少 Full GC 的发生频率。
  • -XX:NewRatio:控制 Young Generation 与 Old Generation 的比例。默认值是 2,表示 Old Generation 占堆内存的 1/3。可以通过调整该参数来优化 Old Generation 和 Young Generation 之间的内存分配比例。

优化原理与效果:

  • 增大 Old Generation:当 Old Generation 空间增大时,老年代对象能够得到更长时间的存活,不容易触发 Full GC。减少 Full GC 的触发频率有助于降低 STW 停顿的发生。
  • 优化内存分配:通过调整 -XX:NewRatio 参数,合理分配 Old Generation 和 Young Generation 的内存,可以避免 Young Generation 空间不足引发的晋升失败(Promotion Failure),并减少晋升到 Old Generation 的压力。

(二)减少 Promotion Failure

1. 背景与原因

Promotion Failure 是指 Young Generation 中的对象无法晋升到 Old Generation,因为 Old Generation 没有足够的空间来容纳这些对象。当发生 Promotion Failure 时,JVM 会触发 Full GC,清理 Old Generation 的空间,从而造成长时间的 STW 停顿。

2. 具体方案

通过调整以下参数,可以减少 Promotion Failure 的发生,降低 Full GC 的频率:

// 减少 Promotion Failure 的发生
-XX:MaxTenuringThreshold=5  // 设置对象在 Young Generation 中存活 5 次后晋升到 Old Generation

// 调整 Survivor 区的比例
-XX:SurvivorRatio=8  // Eden 区和 Survivor 区的比例为 8:1
  • -XX:MaxTenuringThreshold:指定对象在 Young Generation 中存活的次数。只有当对象存活足够多次后,才会被晋升到 Old Generation。调整该参数可以减少频繁晋升到 Old Generation 的对象,从而减轻 Old Generation 的负担。
  • -XX:SurvivorRatio:控制 Young Generation 中 Eden 区和 Survivor 区的大小比例。调整该比例可以增加 Young Generation 空间,减少对象晋升到 Old Generation 的概率。

优化原理与效果:

  • 减少晋升失败:通过调整 -XX:MaxTenuringThreshold,可以控制对象在 Young Generation 中的存活时间,减少过早晋升到 Old Generation 的对象。通过增加 -XX:SurvivorRatio,扩大 Young Generation 中的 Survivor 区,可以容纳更多短生命周期对象,从而降低晋升失败的概率。
  • 减轻 Old Generation 压力:通过减少晋升到 Old Generation 的对象数量,减轻 Old Generation 的内存压力,从而减少频繁触发 Full GC 的概率。

(三)减少内存碎片

1. 背景与原因

CMS 使用标记-清理算法进行垃圾回收。标记-清理算法的一个缺点是会在清理过程中产生内存碎片。大量碎片可能会导致 Old Generation 中无法找到足够大的连续内存块来存储新对象,从而降低内存的使用效率,甚至触发 Full GC。

2. 具体方案

可以通过以下参数来减少内存碎片的影响:

// 开启 Full GC 时的内存压缩
-XX:+UseCMSCompactAtFullCollection  // 在 Full GC 时进行内存压缩

// 控制堆内存空闲比例
-XX:MinHeapFreeRatio=40  // 设置堆内存的最小空闲比例为 40%
-XX:MaxHeapFreeRatio=70  // 设置堆内存的最大空闲比例为 70%
  • -XX:+UseCMSCompactAtFullCollection:在 Full GC 期间,启用内存压缩功能,减少 Old Generation 中的碎片问题。
  • -XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio:控制堆内存的最小和最大空闲比例,通过这些参数可以减少碎片化现象。

优化原理与效果:

  • 内存压缩:通过启用 -XX:+UseCMSCompactAtFullCollection,在 Full GC 期间对 Old Generation 进行内存压缩,回收碎片并整理内存,从而提高内存利用率。
  • 控制堆内存比例:通过合理设置堆的空闲内存比例,可以避免 Old Generation 中过多的空闲内存造成碎片化,提高内存的使用效率。

(四)增加 CMS 的并行度

1. 背景与原因

CMS 的并发标记和清理阶段设计目的是在应用线程运行的同时,标记和清理不再使用的对象。然而,在高并发环境下,如果并发标记和清理的线程数不足,可能会导致标记和清理阶段效率低下,进而增加 STW 停顿时间。

2. 具体方案

可以通过以下参数来增加 CMS 的并行度,提高垃圾回收效率:

// 增加并行标记和清理线程数
-XX:ParallelCMSThreads=8  // 设置并行标记和清理的线程数为 8
  • -XX:ParallelCMSThreads:设置用于并发标记和清理阶段的线程数。通过增加线程数,可以提升并发标记和清理的速度,缩短 GC 停顿时间。

优化原理与效果:

  • 提升并行度:通过增加 -XX:ParallelCMSThreads,可以充分利用多核 CPU 资源,提升并发标记和清理的速度,减少 GC 停顿时间。
  • 减少 STW 停顿时间:增加并行标记和清理线程数,可以缩短 GC 停顿时间,提升系统响应能力。

(五)切换到 G1 GC

1. 背景与原因

G1 GC(Garbage First Garbage Collector)是为了应对大堆内存、高并发场景下的垃圾回收需求而设计的,它采用了分代回收和增量标记的方式,相比于 CMS,G1 更加稳定,且能更好地控制 GC 停顿时间。

2. 具体方案

// 切换到 G1 GC
-XX:+UseG1GC  // 启用 G1 GC 回收器

// 设置 G1 的最大 GC 停顿时间
-XX:MaxGCPauseMillis=200  // 设置最大 GC 停顿时间为 200ms
  • -XX:+UseG1GC:启用 G1 GC 回收器,替代 CMS。
  • -XX:MaxGCPauseMillis:控制 G1 GC 的最大暂停时间,G1 会尝试尽量使 GC 停顿时间不超过该值。

优化原理与效果:

  • 减少 STW 停顿时间:G1 GC 是面向低延迟场景设计的回收器,它能够精确控制每次 GC 停顿的最大时长,避免了 CMS 中长时间停顿的问题。
  • 更高效的内存回收:G1 在进行垃圾回收时,会将内存划分为多个区域,并使用增量标记和并行回收技术,更高效地回收内存。

(六)使用 ZGC 或 Shenandoah GC

1. 背景与原因

ZGC 和 Shenandoah GC 是为了进一步降低垃圾回收时的停顿时间而设计的,它们都属于低延迟回收器,尤其适用于对响应时间要求极高的应用场景。

2. 具体方案

笔者在2022年所接触到的所有服务已经整体升级到了ZGC,其性能各方面确实是最优的。

// 启用 ZGC
-XX:+UseZGC  // 启用 ZGC 回收器

// 启用 Shenandoah GC
-XX:+UseShenandoahGC  // 启用 Shenandoah GC
  • -XX:+UseZGC:启用 ZGC(低延迟垃圾回收器)。
  • -XX:+UseShenandoahGC:启用 Shenandoah GC(低延迟垃圾回收器)。

优化原理与效果:

  • 极低停顿:ZGC 和 Shenandoah GC 采用了多线程的标记和并发清理机制,几乎消除了垃圾回收期间的停顿时间,使应用能够保持较高的吞吐量和低延迟。
  • 适用于低延迟要求:在对响应时间要求较高的系统中,ZGC 和 Shenandoah GC 可以显著改善垃圾回收停顿问题,特别是在大内存场景下。

通过这些优化方案,你可以根据具体的应用场景和性能需求,选择合适的垃圾回收器和调整合适的参数,从而降低 GC 停顿时间,提升系统性能。

五、总结

在高并发环境下,长时间的 CMS Old GC 停顿可能会严重影响 Java 应用的性能和稳定性,尤其是在高延迟要求的系统中。通过深入分析和优化,能够有效减轻 GC 停顿对系统的负面影响。优化的关键点包括调整 Old Generation 大小、减少 Promotion Failure、减少内存碎片、增加并行度等方面。结合合适的 JVM 参数调优,可以降低 Full GC 触发的频率,减少内存碎片,并提升 GC 阶段的并行度,从而有效降低停顿时间。

对于一些高并发和低延迟要求的场景,除了调优 CMS 外,切换到 G1、ZGC 或 Shenandoah GC 也可以带来更好的性能表现。这些回收器采用了更加先进的标记与清理机制,能够更精确地控制停顿时间,适应不同类型应用的需求。

总之,GC 停顿的优化是一个综合性的工作,除了从 JVM 参数入手外,还需要结合应用的实际运行情况,通过持续监控与调整,确保系统稳定性和响应时间满足业务需求。

猜你喜欢

转载自blog.csdn.net/xiaofeng10330111/article/details/146111354
今日推荐