分享

CMS垃圾回收器详解

 码农9527 2021-12-16

 Concurrent Mark Sweep。看名字就知道,CMS是一款并发、使用标记-清除算法的gc。CMS是针对老年代进行回收的GC。

CMS垃圾回收器详解

  一、哪些对象可以回收

  1、引用计数法

  算法过程:

  每个对象有一个引用计数器,当对象被引用一次计数器就加一,引用失效就减一,对于计数器为0的对象表示为垃圾对象,可以被GC回收。

  请分析以下哪些操作是原子性操作:

  缺点:

  无法解决循环引用的问题,例如:A引用了B,B引用了A,但是A和B都没有被其他对象引用,这样就会导致内存泄漏,无法被回收。

  2、可达性分析法

  算法过程:通过一系列被称为GC Roots的对象作为起点开始搜索,所经过的路径被称为引用链,当一个对象没有跟任何一个引用链相连接的时候,表示从GC Roots对象到这个对象不可达,意味着这是一个垃圾对象可以被回收。

  适用场景:Java虚拟机是采用这种算法对垃圾进行回收,解决了循环引用的问题。

  可以作为GC Roots对象有:虚拟机栈,静态成员,常量,本地方法栈引用的对象。

  二、垃圾回收算法

  1、标记清除

  算法过程:首先标记出需要回收的对象,标记完成后统一回收。

  缺点:主要有两个缺点,一个是标记和清除两个过程的效率都不算高(据资料显示)。另一个是空间问题,标记清除后,会产生不连续的内存碎片,当需要分配大对象时,无法找到足够连续的内存,导致分配失败提前触发GC。

  2、复制算法

  算法过程:将内存分为两部分,每次只使用其中一块内存。回收时将存活的对象复制到另一块区域,之后将已使用的内存区域一次性全部清理掉。

  优点:解决了空间碎片的问题,存活对象少时,提升了回收效率。

  缺点:一个是内存使用率缩小了,因为永远有一块空闲的内存备用。另一个是当存活对象较多时复制效率低下。

  3、标记整理

  算法过程:标记整理的算法过程跟标记清除的标记过程是一样的,但标记后是将存活的对象都向某一端移动,然后清理边界以外的内存。

  优点:解决了内存碎片的问题。

  可以作为GC Roots对象有:虚拟机栈,静态成员,常量,本地方法栈引用的对象。

  三、CMS介绍

  1、介绍

  老年代收集器,需要配合Serial或者ParNew使用,一般是与ParNew使用。CMS全称Concurrent Mark-Sweep。CMS出现的目标是为了降低延迟,减少回收停顿时间。适合对延迟、停顿时间敏感的应用使用。通过 -XX:+UseConcMarkSweepGC开启。

  2、回收阶段

  CMS-initial-mark(STW):通过可达性分析算法,从GC Roots对象开始扫描能够直接关联到的对象,并做标记,需要STW,但是一般会很快完成。

  CMS-concurrent-mark:从初始标记的基础上,继续向下搜索并做标记,这个阶段应用线程和GC线程并发执行,不会造成停顿。

  CMS-concurrent-preclean:查找并发标记阶段中,新进入老年代的对象,包括晋升到老年代和直接在老年代分配的对象,目的是减少下一个阶段重新标记的时间。

  CMS-concurrent-abortable-preclean:可中断的预清理,这个阶段做的事情跟并发预清理的事情一样,目的是为了减少下一个阶段STW的时间,这个阶段有几个个条件控制何时结束。

  A、-XX:CMSScheduleRemarkEdenSizeThreshold=2.该阶段在Eden区占用超过2M时启动。

  B、-XX:CMSMaxAbortablePrecleanTime=5000.设置一个最大时间最大执行5秒钟。

  C、-XX:CMSScheduleRemarkEdenPenetration=50.Eden区使用率超过50就停止该阶段进入remark阶段。

  D、-XX:+CMSScavengeBeforeRemark,控制remark阶段前进行一次minor gc,来提高remark的效率,减少时间。

  CMS-remark(STW):重新标记,处理上几次以来可能引用关系发生变化的部分,并重新进行标记,这个阶段会停止应用线程

  CMS-concurrent-sweep:真正的清理阶段,将以上几个步骤标记的无法访问的对象进行并发清理,并将清理的空间回收到空闲列表中

  CMS-concurrent-reset:调整堆大小,重置一些内部的数据结构,为下一次回收做准备

  3、缺点

  A、无法处理浮动垃圾,因为这是一款并发的收集器,程序运行和收集的同时都会产生垃圾,所以回收时不能向其他收集器一样等到满了的时候再收集,通过-XX:CMSInitiatingOccupancyFraction=65设置触发的百分比,留出一定的空间给并发收集的时候使用,当CMS运行期间无法满足程序的需要,则会出现,此时会临时使用Serial Old作为临时方案进行一次标记整理的回收,这样就会使得出现较为长期的停顿,所以-XX:CMSInitiatingOccupancyFraction=65不能设置太高。

  B、由于是使用标记清除算法的收集器,因此会产生内存碎片,为了解决这个问题,垃圾收集器提供了一个-XX:+UseCMSCompactAtFullCollection的开关参数,表示再一次FGC时进行一次内存整理的过程,这个过程是无法并发的,会拉长停顿时间,收集器还提供另一个参数,-XX:CMSFullGCsBeforeCompaction=5表示FGC几次后进行一次内存整理。

  4、优化措施

  优化目标:减少STW停顿时间

  优化手段:

  A、降低触发CMS回收的百分比,给并发收集时留出一定的空间,避免Concurrent Mode Failure,通过-XX:CMSInitiatingOccupancyFraction=65配置

  B、开启内存压缩,避免碎片问题无法分配大内存对象,通过-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction=5.具体数值可以按需调配。

  C、减少CMS-remark(STW)阶段的时间,通过-XX:+CMSScavengeBeforeRemark、-XX:CMSMaxAbortablePrecleanTime=5000、-XX:CMSScheduleRemarkEdenPenetration=50进行微调。

  5、相关参数

参数说明
-Xloggc:/dev/shm/xxx_gc.log设置GC日志路径
-XX:+PrintGCDateStamps打印GC时间
-XX:+PrintGCDetails打印GC详细信息
-XX:+PrintGCApplicationStoppedTime打印应用停顿时间
-XX:+PrintGCApplicationConcurrentTime打印应用当前时间
-XX:+PrintSafepointStatistics打印安全点统计信息
-XX:+UnlockDiagnosticVMOptions解锁诊断型参数
-XX:-DisplayVMOutput禁用控制台输出VM信息,例如安全点日志
-XX:+LogVMOutput启用日志输出VM日志
-XX:LogFile=/dev/shm/safepoint.log设置VM日志输出路径
-XX:CMSScheduleRemarkEdenPenetration=20设置Eden区使用率超过20%就进入remark阶段
-XX:CMSMaxAbortablePrecleanTime=1500设置可中断预清理阶段最大执行时间
-XX:+CMSScavengeBeforeRemark设置期望进入remark阶段前进行一次MinorGC
-XX:+PrintReferenceGC打印引用处理信息
-XX:+UseConcMarkSweepGC使用CMS收集器
-XX:+UseParNewGC使用ParNew收集器
-XX:+CMSParallelRemarkEnabledremark阶段采用并行多线程模式
-XX:+UseCMSCompactAtFullCollection开启压缩整理功能
-XX:CMSFullGCsBeforeCompaction=0设置CMS多少次后进行一次压缩
-XX:+CMSClassUnloadingEnabled开启回收永久代
-XX:+UseCMSInitiatingOccupancyOnly设置CMS回收时机一直按照Fraction配置进行
-XX:CMSInitiatingOccupancyFraction=60设置老年代内存使用率超过60时开始CMS回收
-XX:+AlwaysPreTouch启动时申请所需内存,避免系统调用延迟

  6、日志分析

  以下是一次完整的CMS回收日志,需要配置以下参数 

2019-07-17T21:54:50.216+0800: 190007.616: [GC [1 CMS-initial-mark: 1258440K(2097152K)] 1294890K(3984640K), 0.0365050 secs] [Times: user=0.04 sys=0.01, real=0.04 secs]2019-07-17T21:54:50.253+0800: 190007.654: Total time for which application threads were stopped: 0.0455330 seconds2019-07-17T21:54:50.254+0800: 190007.654: [CMS-concurrent-mark-start]2019-07-17T21:54:50.413+0800: 190007.813: [CMS-concurrent-mark: 0.159/0.159 secs] [Times: user=0.91 sys=0.02, real=0.16 secs]2019-07-17T21:54:50.413+0800: 190007.814: [CMS-concurrent-preclean-start]2019-07-17T21:54:50.413+0800: 190007.814: [Preclean SoftReferences, 0.0005560 secs]2019-07-17T21:54:50.414+0800: 190007.814: [Preclean WeakReferences, 0.0011690 secs]2019-07-17T21:54:50.415+0800: 190007.815: [Preclean FinalReferences, 0.0000350 secs]2019-07-17T21:54:50.415+0800: 190007.815: [Preclean PhantomReferences, 0.0000070 secs]2019-07-17T21:54:50.425+0800: 190007.825: [CMS-concurrent-preclean: 0.011/0.011 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]2019-07-17T21:54:50.425+0800: 190007.826: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time 2019-07-17T21:54:51.938+0800: 190009.338: [CMS-concurrent-abortable-preclean: 1.508/1.512 secs] [Times: user=2.46 sys=0.16, real=1.51 secs]2019-07-17T21:54:51.938+0800: 190009.338: Application time: 1.6846120 seconds2019-07-17T21:54:51.946+0800: 190009.346: [GC[YG occupancy: 351451 K (1887488 K)]2019-07-17T21:54:51.946+0800: 190009.347: [GC2019-07-17T21:54:51.949+0800: 190009.350: [ParNew2019-07-17T21:54:51.979+0800: 190009.379: [SoftReference, 2284 refs, 0.0002550 secs]2019-07-17T21:54:51.979+0800: 190009.379: [WeakReference, 5 refs, 0.0000150 secs]2019-07-17T21:54:51.979+0800: 190009.379: [FinalReference, 679 refs, 0.0002280 secs]2019-07-17T21:54:51.979+0800: 190009.380: [PhantomReference, 0 refs, 0.0000120 secs]2019-07-17T21:54:51.979+0800: 190009.380: [JNI Weak Reference, 0.0000100 secs]: 351451K->23085K(1887488K), 0.0330260 secs] 1609892K->1282310K(3984640K), 0.0370030 secs] [Times: user=0.26 sys=0.01, real=0.04 secs]2019-07-17T21:54:51.984+0800: 190009.384: [Rescan (parallel) , 0.0272010 secs]2019-07-17T21:54:52.011+0800: 190009.411: [weak refs processing2019-07-17T21:54:52.011+0800: 190009.411: [SoftReference, 5341 refs, 0.0020150 secs]2019-07-17T21:54:52.013+0800: 190009.413: [WeakReference, 0 refs, 0.0000220 secs]2019-07-17T21:54:52.013+0800: 190009.413: [FinalReference, 80 refs, 0.0002020 secs]2019-07-17T21:54:52.013+0800: 190009.414: [PhantomReference, 0 refs, 0.0000120 secs]2019-07-17T21:54:52.013+0800: 190009.414: [JNI Weak Reference, 0.0000230 secs], 0.0023470 secs]2019-07-17T21:54:52.013+0800: 190009.414: [class unloading, 0.0198310 secs]2019-07-17T21:54:52.033+0800: 190009.434: [scrub symbol table, 0.0100610 secs]2019-07-17T21:54:52.043+0800: 190009.444: [scrub string table, 0.0011130 secs] [1 CMS-remark: 1259224K(2097152K)] 1282310K(3984640K), 0.1204410 secs] [Times: user=0.48 sys=0.02, real=0.12 secs]2019-07-17T21:54:52.067+0800: 190009.468: Total time for which application threads were stopped: 0.1293860 seconds2019-07-17T21:54:52.068+0800: 190009.468: [CMS-concurrent-sweep-start]2019-07-17T21:54:52.747+0800: 190010.147: [CMS-concurrent-sweep: 0.679/0.679 secs] [Times: user=1.24 sys=0.09, real=0.68 secs]2019-07-17T21:54:52.747+0800: 190010.147: [CMS-concurrent-reset-start]2019-07-17T21:54:52.753+0800: 190010.153: [CMS-concurrent-reset: 0.006/0.006 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]12345678910111213141516复制代码类型:[java]

  通用字段解析

日志内容含义
2019-07-17T21:54:50.216+0800GC发生的时间,系统当前时间
190007.616虚拟机从启动到现在经过的时间
[Times: user=0.04 sys=0.01, real=0.04 secs]当前过程耗时情况,user表示运行用户代码时间,sys表示系统调用以及系统级等待的时间,real表示实际时间

  CMS-initial-mark

日志内容含义
1258440K(2097152K)1258440K:回收前老年代占用大小,2097152K:老年代总大小
1294890K(3984640K)1294890K:整堆内存占用大小,3984640K:堆内存总大小

  CMS-concurrent-mark

日志内容含义
[Preclean SoftReferences, 0.0005560 secs]该阶段处理软引用的时间
[Preclean WeakReferences, 0.0011690 secs]该阶段处理弱引用的时间
[Preclean FinalReferences, 0.0000350 secs]该阶段处理最终引用的时间
[Preclean PhantomReferences, 0.0000070 secs]该阶段处理虚引用的时间
[CMS-concurrent-preclean: 0.011/0.011 secs]预清理阶段耗费的时间

  CMS-concurrent-abortable-preclean

日志内容含义
[CMS-concurrent-abortable-preclean: 1.508/1.512 secs]可中断预清理阶段耗费的时间,该阶段并行,不会STW

  YG occupancy

日志内容含义
GC[YG occupancy: 351451 K (1887488 K)表示该阶段是MinorGC,351451表示年轻代占用大小,1887488年轻代总大小
351451K->23085K(1887488K), 0.0330260 secs351451K年轻代回收前大小,23085K年轻代回收后大小,1887488K年轻代总大小,耗时0.0330260

  CMS-remark

日志内容含义
[Rescan (parallel) , 0.0272010 secs]重新标记花费的时间
[class unloading, 0.0198310 secs]类卸载花费的时间
[scrub symbol table, 0.0100610 secs]刷新符号表耗费的时间
[scrub string table, 0.0011130 secs]刷新常量池耗费的时间?
[1 CMS-remark: 1259224K(2097152K)] 1282310K(3984640K), 0.1204410 secs]1259224K老年代占用大小,2097152K老年代总大小,1282310K堆内存占用大小,3984640K堆内存总大小,耗时0.1204410s

  四、实战

  1、测试代码

/**
 * 堆溢出测试
 * -Xms100m -Xmx100m -XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
 *
 * @author Horace
 */public class HeapOOMTest {private static Logger logger = LoggerFactory.getLogger(HeapOOMTest.class);public static void main(String[] args) {int _1M = 1024 * 1024;
  List<byte[]> bytes = new ArrayList<>();while (true) {
   bytes.add(new byte[_1M]);
   LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
  }
 }
}1234567891011121314151617复制代码类型:[java]

  2、查看GC情况

  本地环境界面查看工具 jvisualvm

  每1秒输出一次GC情况

  jstat -gccause pid 1s

  以下是GC情况

S0  S1  E   O   M  CCS YGC  YGCT FGC FGCT  GCT LGCC  GCC 89.83   0.00  42.05  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  46.58  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  51.00  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  54.74  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  58.49  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  63.69  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  67.44  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  71.19  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  75.71  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  79.46  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  83.98  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  87.72  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC 89.83   0.00  92.14  37.10  94.37  90.41   2 0.025  0 0.000 0.025 Allocation Failure   No GC1234567891011121314复制代码类型:[java]

  各个字段表示的含义可以查看:

  https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html#BEHHGFAE

  命令行dump内存快照

  jmap -heap dump:live,format=b,file=/tmp/dump.hprof

  dump出来的内存可以通过jprofile、mat、jhat等工具进行分析

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多