jvm性能调优 - 15JVM的老年代垃圾回收器CMS的缺点

简介: jvm性能调优 - 15JVM的老年代垃圾回收器CMS的缺点

Pre

上篇文章用一步一图的方式给大家讲清楚了CMS垃圾回收的运行机制

简单来说,为了避免长时间“Stop the World”,CMS采用了4个阶段来垃圾回收,其中初始标记和重新标记,耗时很短,虽然会导致“Stop the World”,但是影响不大。

然后并发标记和并发清理,两个阶段耗时最长,但是是可以跟系统的工作线程并发运行的,所以对系统没太大影响。

这就是CMS的基本工作原理。

但是本文要更加深入的去说一说CMS垃圾回收期间的一些细节问题,同时给出CMS常见的JVM参数应该如何设置。


并发回收垃圾导致CPU资源紧张

首先大家回顾一下这个图。

CMS垃圾回收器有一个最大的问题,虽然能在垃圾回收的同时让系统同时工作,但是大家发现没有,在并发标记和并发清理两个最耗时的阶段,垃圾回收线程和系统工作线程同时工作,会导致有限的CPU资源被垃圾回收线程占用了一部分。

并发标记的时候,需要对GC Roots进行深度追踪,看所有对象里面到底有多少人是存活的

但是因为老年代里存活对象是比较多的,这个过程会追踪大量的对象,所以耗时较高。并发清理,又需要把垃圾对象从各种随机的内存位置清理掉,也是比较耗时的。

所以在这两个阶段,CMS的垃圾回收线程是比较耗费CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4

我们用最普通的2核4G机器和4核8G机器来计算一下,假设是2核CPU,本来CPU资源就有限,结果此时CMS还会有个“(2 + 3) / 4” = 1个垃圾回收线程,去占用宝贵的一个CPU。

所以其实CMS这个并发垃圾回收的机制,第一个问题就是会消耗CPU资源。


Concurrent Mode Failure问题

第二个问题,是很多同学都很关注的一个问题,就是如下图

在并发清理阶段,CMS只不过是回收之前标记好的垃圾对象

但是这个阶段系统一直在运行,可能会随着系统运行让一些对象进入老年代,同时还变成垃圾对象,这种垃圾对象是“浮动垃圾”。

大家看上图那个红圈画的地方,那个对象就是在并发清理期间,系统程序可能先把某些对象分配在新生代,然后可能触发了一次Minor GC,一些对象进入了老年代,然后短时间内又没人引用这些对象了。

这种对象,就是老年代的“浮动垃圾”。

因为他虽然成为了垃圾,但是CMS只能回收之前标记出来的垃圾对象,不会回收他们,需要等到下一次GC的时候才会回收他们。

所以为了保证在CMS垃圾回收期间,还有一定的内存空间让一些对象可以进入老年代,一般会预留一些空间。

CMS垃圾回收的触发时机,其中有一个就是当老年代内存占用达到一定比例了,就自动执行GC。

“-XX:CMSInitiatingOccupancyFaction”参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6里面默认的值是92%。

也就是说,老年代占用了92%空间了,就自动进行CMS垃圾回收,预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代中。

那么如果CMS垃圾回收期间,系统程序要放入老年代的对象大于了可用内存空间,此时会如何?

这个时候,会发生Concurrent Mode Failure,就是说并发垃圾回收失败了,我一边回收,你一边把对象放入老年代,内存都不够了。

此时就会自动用“Serial Old”垃圾回收器替代CMS,就是直接强行把系统程序“Stop the World”,重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生

然后一次性把垃圾对象都回收掉,完事儿了再恢复系统线程。

所以在生产实践中,这个自动触发CMS垃圾回收的比例需要合理优化一下,避免“Concurrent Mode Failure”问题


内存碎片问题

之前给大家说过内存碎片的问题,就是老年代的CMS采用“标记-清理”算法,每次都是标记出来垃圾对象,然后一次性回收掉,这样会导致大量的内存碎片产生。

如果内存碎片太多,会导致后续对象进入老年代找不到可用的连续内存空间了,然后触发Full GC。

所以CMS不是完全就仅仅用“标记-清理”算法的,因为太多的内存碎片实际上会导致更加频繁的Full GC。

CMS有一个参数是“-XX:+UseCMSCompactAtFullCollection”,默认就打开了

他意思是在Full GC之后要再次进行“Stop the World”,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。

还有一个参数是“-XX:CMSFullGCsBeforeCompaction”,这个意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理。

如下图所示:

上图有一个画红圈的地方,就是说在垃圾回收之后,有一些内存碎片,接着会停止工作线程进行碎片整理,如下图:

大家可以看到,内存碎片整理完之后,存活对象都放在一起,然后空出来大片连续内存空间可供使用。


什么情况下触发老年代GC

  • 第一是老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;
  • 第二是老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;
  • 第三是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。

上述情况都会导致老年代Full GC。

  • 今天加了一个触发时机,就是“-XX:CMSInitiatingOccupancyFaction”参数

刨除掉上述几种情况,如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC。

希望大家认真回顾一下上述过程,把老年代Full GC的几个时机都仔细梳理一下。


相关文章
|
9月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
846 55
|
11月前
|
Prometheus 监控 算法
CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究
本文介绍了CMS(Concurrent Mark-Sweep)垃圾回收器的工作原理、优缺点及常见问题,并通过具体案例分析了其优化策略。重点探讨了CMS的各个阶段,包括标记、并发清理和重标记
CMS圣经:CMS垃圾回收器的原理、调优,多标+漏标+浮动垃圾 分析与 研究
|
11月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
9月前
|
缓存 算法 Java
JVM深入原理(八)(一):垃圾回收
弱引用-作用:JVM中使用WeakReference对象来实现软引用,一般在ThreadLocal中,当进行垃圾回收时,被弱引用对象引用的对象就直接被回收.软引用-作用:JVM中使用SoftReference对象来实现软引用,一般在缓存中使用,当程序内存不足时,被引用的对象就会被回收.强引用-作用:可达性算法描述的根对象引用普通对象的引用,指的就是强引用,只要有这层关系存在,被引用的对象就会不被垃圾回收。引用计数法-缺点:如果两个对象循环引用,而又没有其他的对象来引用它们,这样就造成垃圾堆积。
236 0
|
9月前
|
算法 Java 对象存储
JVM深入原理(八)(二):垃圾回收
Java垃圾回收过程会通过单独的GC线程来完成,但是不管使用哪一种GC算法,都会有部分阶段需要停止所有的用户线程。这个过程被称之为StopTheWorld简称STW,如果STW时间过长则会影响用户的使用。一般来说,堆内存越大,最大STW就越长,想减少最大STW,就会减少吞吐量,不同的GC算法适用于不同的场景。分代回收算法将整个堆中的区域划分为新生代和老年代。--超过新生代大小的大对象会直接晋升到老年代。
232 0
|
11月前
|
存储 监控 架构师
ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究
ZGC圣经:ZGC垃圾回收器的原理、调优,ZGC 漏标的 分析与 研究
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
4月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
386 5
|
4月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。