JVM学习笔记24 CMS 垃圾收集器

bilibili-JVM学习笔记24 CMS 垃圾收集器
The Java Virtual Machine Specification - Java SE 8 Edition

JVM学习笔记18 字节码知识总结
JVM学习笔记19 JVM内存空间
JVM学习笔记20 jvisualvm
JVM学习笔记21 java 工具
JVM学习笔记22 垃圾回收理论知识
JVM学习笔记23内存泄漏 和 VM参数设置

基于 java 1.8.0

安全点与安全区域详解 83

Concurrent Mark Sweep --> CMS

  • 枚举根节点
    • 当执行系统停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得知哪些地方存放着对象引用。在 HotSpot 的实现中,使用一组称为 OopMap 的数据结构来达到这个目的的;
  • 安全点 Safepoint
    • 在 OopMap 的协助下,HotSpot 可以快速且准确地完成 GC Roots 枚举,但一个很现实的问题随之而来:可能导致引用关系变化,或者说 OopMap 内容变化的指令非常多,如果为每一条指令都生成对应的 OopMap ,那将会需要大量的额外空间,这样 GC 的空间成本将会变得更高;
    • 实际上,HotSpot 并没有为每条指令都生成 OopMap ,而只是在特定的位置记录了这些信息,这些位置称为安全点Safepoint),即程序执行时并非在所有地方都能停顿下来开始 GC,只有在到达安全点时才能暂停。
    • Safepoint 的选定既不能太少以至于让 GC 等待时间太长,也不能过于频繁以至于过分增大运行时的负载。所以,安全点的选定基本上是以是否具有让程序长时间执行的特征为标准进行选定的–因为每条指令执行的时间非常短暂,程序不太可能因为指令流长度太长这个原因而过长时间运行,长时间执行的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所有具有这些功能的指令才会产生 Safepoint 。
    • 对于 Safepoint ,另一个需要考虑的问题是如何在 GC 发生时让所有线程(不包括执行 JNI 调用的线程)都跑到最近的安全点上再停顿下来?
      • 抢占式中断 Preemptive Suspension
        • 不需要线程的执行代码主动去配合,在 GC 发生时,首先把所有的线程全部中断,如果有线程中断的地方不在安全点上,就恢复线程,让它跑到安全点上;
      • 主动式中断 Voluntary Suspension
        • 当 GC 需要中断线程的时候,不直接对线程进行操作,仅仅简单的设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
      • 现在几乎没有虚拟机采用抢占式中断来暂停线程从而响应 GC 事件;
  • 安全区域 Safe Regin
    • 在使用 Safeponint 似乎已经完美地解决了如何进入 GC 的问题,但实际上情况却并不一定。Safepoint 机制保证了程序执行时,在不太长的时间内就会遇到可进入 GC 的 Safepoint 。但如果程序在不执行的时候呢?所谓程序不执行就是没有分配 CPU 时间,典型的例子就是线程处于 Sleep 状态或者 Blocked 状态,这时候线程无法响应 JVM 的中断请求,JVM 也显然不太可能等待线程重新分配 CPU 时间。对于这种情况,就需要安全区域来解决了。
    • 在线程执行到安全区域中的代码时,首先标识自己已经进入了安全区域,那么,当在这段时间里 JVM 要发起 GC 时,就不用管标识自己为安全区域状态的线程了。在线程要离开安全区域时,它要检查系统是否已经完成了根节点枚举(或者是整个 GC 过程),如果完成了,那么线程就继续执行,否则它就必须等待直到收到可以安全离开安全区域的信号为止。

CMS垃圾收集器深入讲解 84

CMS 垃圾收集器

  • CMS ,以获取最短回收停顿时间为目标,多数应用于互联网站或者B/S系统的服务器端上;
  • CMS 是基于 标记-清除 算法实现的,整个过程分为 4 个步骤:
    • 初始标记(CMS inital mark)
    • 并发标记(CMS concurrent mark)
    • 重新标记(CMS remark)
    • 并发清除(CMS concurrent sweep)
  • CMS
    • 其中,初始标记、重新标记 这两个步骤仍然需要 Stop The World
    • 初始标记只是标记一下 GC Roots 能直接关联到的对象,速度很快;
    • 并发标记阶段就是进行 GC Roots Tracing 的过程;
    • 重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记采产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍微长一些,但远比并发标记的时间要短。
  • CMS 收集器的运作步骤如下图所示,在整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,因此,从总体上看,CMS 收集器的内存回收过程是与用户线程一起并发执行的。

在这里插入图片描述

  • CMS 优点
    • 并发收集
    • 低停顿
  • CMS 缺点
    • CMS 对 CPU 资源非常敏感;
    • 无法处理浮动垃圾 Floating Garbage,可能出现 Concurrent Mode Failure 失败而导致另一次 Full GC 的产生,可能引发串行 Full GC。如果在应用中老年代增长不是太快,可以适当调高参数 -XX:CMSInitiatingOccuoancyFraction 的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能。要是 CMS 运行期间预留的内存无法满足程序需要时,虚拟机将启动后备预案:临时启动 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就更长了。所以说参数 -XX:CMSInitiatingOccuoancyFraction 设置的太高很容易导致大量 Concurrent Mode Failure 失败,性能反而降低。
    • 收集结束时会产生大量的空间碎片,空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前进行一次 Full GC,CMS 收集器提供了一个参数 : -XX:+UseCMSCompactAtFullCollection 开关(默认开启),用于在 CMS 收集器顶不住要进行 Full GC 时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
    • 对于堆比较大的应用,GC 的时间难以预估。
  • 空间分配担保
    • 在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全的。
    • 当大量对象在 Minor GC 后仍然存活,这就需要老年代进行空间分配担保,把 Survivor 无法容纳的对象直接进入老年代。如果老年代判断到剩余空间不足,则进行一次 Full GC。(根据以往每一次回收晋升到老年代对象容量的平均值作为经验值)。

JDK DocumentHotSpot Virtual Machine Garbage Collection Tuning Guide

CMS 收集器完整收集步骤:

  • Phase 1 : Initial Mark
    • STW
    • 标记那些直接被 GC Roots 引用或者被年轻代存活对象所引用的所有对象
    • 在这里插入图片描述
  • Phase 2 : Concurrent Mark
    • 垃圾收集器会遍历老年代,然后标记所有存活的对象,他会根据上个阶段找到的 GC Roots 遍历查找。并发标记阶段,它会与用户的应用程序并发运行。并不是老年代所有的存活对象都会被标记,因为在标记期间用户的程序可能会改变一些引用。
    • 在这里插入图片描述
    • 与阶段1中图相比,会发现有一个对象的引用已经发生了改变。
  • Phase 3 : Concurrent Preclean
    • 与用户线程并发执行
    • 在阶段2,一些对象的引用可能会发生变化,但是这种情况发生时,JVM 会将包含这个对象的区域(Card)标记为 Dirty ,这就是 Card Marking
    • 在 pre-clean 阶段,那些能够从 Dirty 对象到达的对象也会被标记,这个标记做完后,dirty card 标记就会被清除。
    • 在这里插入图片描述
  • Phase 4 : Concurrent Abortable Preclean
    • 是为了尽量承担 STW 中最终标记阶段的工作,这个阶段持续时间依赖于很多的因素,由于这个阶段是在重复做很多相同的工作,直接满足一些条件(比如:重复迭代的次数、完成的工作量或者时钟时间等)
  • Phase 5 : Final Remark
    • STW
    • 标记老年代所有的存活对象,由于之前的阶段是并发执行的,gc 线程可能跟不上应用程序的变化,为了完成标记老年代所有存活对象的目标,STW 就非常有必要了。
    • 通常 CMS 的 Final Remark 阶段会在年轻代尽可能干净的时候运行,目的是为了减少连续 STW 发生的可能性(年轻代存活对象过多的话,也会导致老年代设计的存活对象过多),这个阶段会比前面的几个阶段更复杂一些。
    • 经历过这五个阶段之后,老年代所有存活的对象都被标记完成了。
  • Phase 6 : Concurrent Sweep
    • 清除那些不再使用的对象,回收内存
  • Phase 7 : Concurrent Reset
    • 重置 CMS 内部的数据结构,为下次的 GC 做准备。

CMS 通过将大量工作分散到并发处理阶段来减少 STW 时间;

实例透彻分析CMS垃圾收集器执行过程 85

package new_package.jvm.p83;

/**
 * -verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC
 */
public class MyTest {

    public static void main(String[] args) {

        int sizeM = 1024 * 1024;
        byte[] bytes1 = new byte[4 * sizeM];
        System.out.println("11111111");
        byte[] bytes2 = new byte[4 * sizeM];
        System.out.println("22222222");
        byte[] bytes3 = new byte[4 * sizeM];
        System.out.println("33333333");
        byte[] bytes4 = new byte[2 * sizeM];
        System.out.println("44444444");

        System.out.println("hello world");
    }
}

11111111
[GC (Allocation Failure) [ParNew: 6616K->595K(9216K), 0.0069552 secs] 6616K->4693K(19456K), 0.0069915 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
22222222
[GC (Allocation Failure) [ParNew: 4846K->108K(9216K), 0.0040079 secs] 8944K->8843K(19456K), 0.0040301 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8734K(10240K)] 12939K(19456K), 0.0001778 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
33333333
44444444
hello world
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 6492 K (9216 K)][Rescan (parallel) , 0.0000792 secs][weak refs processing, 0.0000071 secs][class unloading, 0.0002851 secs][scrub symbol table, 0.0005528 secs][scrub string table, 0.0001244 secs][1 CMS-remark: 8734K(10240K)] 15227K(19456K), 0.0010957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation  [CMS-concurrent-reset-start]
 total 9216K, used 6656K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  79% used [0x00000007bec00000, 0x00000007bf264d60, 0x00000007bf400000)
  from space 1024K,  10% used [0x00000007bf400000, 0x00000007bf41b3d8, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
[CMS-concurrent-reset: 0.000/0.000 secs] concurrent mark-sweep generation [Times: user=0.00 sys=0.00, real=0.00 secs] 
 total 10240K, used 8733K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 3241K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 346K, capacity 388K, committed 512K, reserved 1048576K

JVM垃圾回收重点内容阶段性复习与总结 86

CMS垃圾回收器重要内容回顾与总结 87

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值