JVM 性能分析 —— CMS 老年代并发 GC 触发条件与压缩式 GC (升级为 Full GC)触发条件

文章目录

  • [CMS 触发老年代 GC 条件](#CMS 触发老年代 GC 条件)
    • [foreground collector(前台收集)](#foreground collector(前台收集))
    • [background collector(后台收集)](#background collector(后台收集))
  • [MSC(mark-sweep-compact 压缩式 GC,等价于 Full GC)](#MSC(mark-sweep-compact 压缩式 GC,等价于 Full GC))

CMS 触发老年代 GC 条件

CMS GC 在实现上分成 foreground collector(前台收集)和 background collector(后台收集)。前台收集相对比较简单,后台收集比较复杂,情况比较多。不管是 foreground collector 还是 background collector 使用的都是 mark-sweep 算法,分阶段进行标记清理,优点很明显-低延时,但最大的缺点是存在碎片,内存空间利用率低。

CMS GC 可以触发压缩式 GC(标记压缩算法),就是触发了 Full GC,否则还是正常的并发收集。压缩式 GC 这个过程就必须要等待内存分配成功后业务线程才能继续往下面走,因此整个过程必须 STW。

foreground collector(前台收集)

  • foreground collector 触发条件比较简单,就是没有空间分配对象时,就会触发一次 CMS 并发 GC 收集来进行空间回收。

background collector(后台收集)

CMS 大家都知道是并发收集器,一般常见的并发收集就是正常情况下触发 background collector 的 CMS GC,对业务影响很小。当并发模式搞不定了,就会触发 Full GC,这个回收过程业务线程是不可用的,这时候其实就是触发了 FullGC。

background collector 是通过 CMS 后台线程不断的去扫描,主要是判断是否符合 background collector 的触发条件,一旦有符合的情况,就会进行一次 background 的收集。(参考资料

  • CMSWaitDuration 参数是用于控制 CMS 后台线程扫描的间隔周期,每次扫描去判断是否满足 CMS background collector 的触发条件以开启一次并发收集。CMSWaitDuration 默认值为 2000 毫秒,即 2 秒。

  • 触发条件一:GC cause 是 gclocker 并且配置了 GCLockerInvokesConcurrent 参数、或者 GC cause 是javalangsystemgc(就是 System.gc()调用)并且配置了 ExplicitGCInvokesConcurrent 参数,这时会触发一次 background collector。

    GCLockerInvokesConcurrent 参数用于控制当 GC Locker 被占用时是否触发并发 GC 收集。默认值为 false,即当 GC Locker 被占用时,CMS 收集器会等待 GC Locker 被释放后再进行 Full GC。如果设置为 true,当 GC Locker 被占用时,CMS 收集器会触发并发 GC 收集,而不是等待 GC Locker 被释放后再进行 Full GC。

    ExplicitGCInvokesConcurrent 参数用于控制是否在应用程序调用 System.gc() 时触发并发 GC 收集。默认值为 false,即在应用程序调用 System.gc() 时会触发一次 Full GC。如果设置为 true,当应用程序调用 System.gc() 时,CMS 收集器会触发一次并发 GC 收集,而不是 Full GC。

  • 触发条件二:根据统计数据动态计算(前提是未配置 UseCMSInitiatingOccupancyOnly 时),会根据统计数据动态判断是否需要进行一次 CMS GC。判断逻辑是,如果预测 CMS GC 完成所需要的时间大于预计的老年代将要填满的时间,则进行并发 GC。 这些判断是需要基于历史的 CMS GC 统计指标,然而,第一次 CMS GC 时,统计数据还没有形成,是无效的,这时会跟据 Old Gen 的使用占比来判断是否要进行 GC。

    UseCMSInitiatingOccupancyOnly 参数用于控制是否仅使用设定的 CMS 初始占用率作为触发 CMS 并发 GC 收集的条件。默认值为 false,即 CMS 收集器会使用其他一些条件来决定何时启动并发收集。如果设置为 true,CMS 收集器将仅使用设定的 CMS 初始占用率作为启动并发收集的触发条件,不考虑其他因素。

  • 触发条件三:根据 Old Gen 使用占比来触发。当 Old Gen 的使用占比超过指定阈值(CMSInitiatingOccupancyFraction )就进行 GC。

    CMSInitiatingOccupancyFraction 参数用于设置触发 CMS GC 的老年代占用率阈值。即当老年代使用率超过该参数设定阈值,就触发 GC。

  • 触发条件四:在两代的 GC 体系中,主要指的是 Young GC 是否会失败。如果 Young GC 已经失败或者可能会失败,JVM 就认为需要进行一次 CMS 并发 GC。前提是未开启 UseCMSCompactAtFullCollection。

    Young GC 为什么会失败一般是因为 Old Gen 没有足够的空间来容纳晋升的对象,比如常见的 "promotion failed";

    可能失败是通过判断当前 Old Gen 剩余的空间大小是否足够容纳 Young GC 晋升的对象大小。 Young GC 到底要晋升多少是无法提前知道的,因此,这里通过统计平均每次 Young GC 晋升的大小和当前 Young GC 可能晋生的最大大小来进行比较;

  • 触发条件五:配置了 CMSClassUnloadingEnabled 参数,在 meta space 进行扩容时就会进行一次 CMS 并发 GC。(经常会有应用启动不久,Old Gen 空间占比还很小的情况下,进行了一次 CMS GC,让你很莫名其妙,其实就是这个原因导致的。)

    CMSClassUnloadingEnabled 参数用于控制 CMS GC 是否启用类卸载功能。类卸载是 JVM 在 GC 过程中释放不再使用的类元数据的过程。这样可以减少永久代或元空间的占用,从而提高内存的利用效率。

    • CMSClassUnloadingEnabled 参数默认为 true,CMS GC 会在 GC 过程中执行类卸载。
    • 当 CMSClassUnloadingEnabled 参数设置为 false 时,CMS GC 不会执行类卸载。

MSC(mark-sweep-compact 压缩式 GC,等价于 Full GC)

CMS 在每次进行 GC 之前,会先判断是否需要进行一次压缩式 GC。此压缩式 GC,CMS 使用的是跟 Serial Old GC 一样的 LISP2 算法,其使用 mark-compact 来做 Full GC,一般称之为 MSC(mark-sweep-compact),它收集的范围是 Java 堆的 Young Gen 和 Old Gen 以及 metaspace(元空间)。

CMS 在进行 GC 之前,会先判断是进行 mark-sweep 的 CMS GC,还是进行 mark-sweep-compact 的 Full GC。主要的判断依据是:(参考资料

  • 前提条件:需要开启 UseCMSCompactAtFullCollection(-XX:UseCMSCompactAtFullCollection=true 或 -XX:+UseCMSCompactAtFullCollection),默认情况下是开启的。该参数决定了在某个触发条件下是否是触发压缩式 GC,以减少内存碎片。

  • 触发条件一:在上次压缩式的 Full GC 后,又发生的 CMS GC 次数大于等于 CMSFullGCsBeforeCompaction 参数设定的阈值,就表示可以进行一次压缩式的 Full GC。

    CMSFullGCsBeforeCompaction 参数用于控制在多少次 CMS GC 之后触发一次内存压缩操作。默认值为 0,表示在每次 CMS GC 时都会触发内存压缩。

  • 触发条件二:用户请求式触发导致的 GCCause,就是 javalangsystemgc(即 System.gc())或者 jvmtiforce_gc(即 JVMTI 方式的强制 GC),意味着只要是 System.gc(前提没有配置 ExplicitGCInvokesConcurrent 参数)调用或者 JVMTI 方式的强制 GC 都会进行一次压缩式的 Full GC。

    ExplicitGCInvokesConcurrent 参数用于控制是否在应用程序调用 System.gc() 时触发并发 GC 收集。默认值为 false,即在应用程序调用 System.gc() 时会触发一次 Full GC。如果设置为 true,当应用程序调用 System.gc() 时,CMS 收集器会触发一次并发 GC 收集,而不是 Full GC。

  • 触发条件三:在两代的 GC 体系中,主要指的是 Young GC 是否会失败。如果 Young GC 已经失败或者可能会失败,CMS 就认为可能存在碎片导致的,需要进行一次压缩式的 Full GC。

    Young GC 为什么会失败一般是因为 Old Gen 没有足够的空间来容纳晋升的对象,比如常见的 "promotion failed";

    可能失败是通过判断当前 Old Gen 剩余的空间大小是否足够容纳 Young GC 晋升的对象大小。 Young GC 到底要晋升多少是无法提前知道的,因此,这里通过统计平均每次 Young GC 晋升的大小和当前 Young GC 可能晋生的最大大小来进行比较;

  • 触发条件四:是否清理所有 SoftReference。在配置了 CMSCompactWhenClearAllSoftRefs 参数的情况下,会进行一次压缩式的 Full GC。

    CMSCompactWhenClearAllSoftRefs 用于控制在 CMS 收集器清理完所有软引用对象后是否触发内存压缩。默认值为 false,即在清理完软引用对象后不会触发内存压缩。

在 GC 调优时应该尽可能的避免压缩式的 Full GC,因为其使用的是 Serial Old GC 类似算法,它是单线程对全堆以及 metaspace 进行回收,STW 的时间会特别长,对业务系统的可用性影响比较大。

相关推荐
努力进修22 分钟前
欢迎来到我的Java世界“抽象类”
java·开发语言·python
Lilixy.182324 分钟前
【Java-反射】
java·开发语言
立志成为coding大牛的菜鸟.1 小时前
力扣139-单词拆分(Java详细题解)
java·算法·leetcode
星夜孤帆2 小时前
LeetCode之数组/字符串
java·算法·leetcode
Ai 编码助手2 小时前
什么是内存溢出,golang是如何解决内存溢出的
jvm·golang
GISer小浪花努力上岸5 小时前
Java实现简易计算器功能(idea)
java·开发语言·intellij-idea
海海向前冲5 小时前
设计模式 -- 单例设计模式
java·开发语言·设计模式
就这样很好8805 小时前
排序算法总结
java·算法·排序算法
weixin_486681145 小时前
C++系列-STL中find相关的算法
java·c++·算法
学java的小菜鸟啊6 小时前
Java队列详细解释
java·开发语言·经验分享·python