jvm之垃圾收集二之常用垃圾收集器

  前面简单介绍了如何确定对象是垃圾、什么时候回收、怎么回收,今天就来聊一聊java中常见的垃圾回收器,从Serial到G1,其中会着重解读CMS和G1的工作原理,包括如何安全的并发回收、cSet、rSet等以及他们各自的优缺点;下面按照新生代、老年代进行分类介绍

新生代垃圾回收器

一、Serial收集器(STW)

  Serial收集器是一个单线程的、采用标记-复制算法的、回收时需要暂停用户线程(STW)的新生代垃圾回收器;可与serial old或cms搭配使用;但JDK9后已不再支持Serial与CMS的组合

二、ParNew收集器(STW)

  ParNew是Serial的多线程版,即垃圾回收线程有多个,但是也是需要STW,回收算法采用的也是标记-复制,除了Serial收集器外只有它可与CMS搭配工作;但自JDK9后已不再支持其与SerialOld的组合,加上上文说的也不支持Serial与CMS的组合,所以JDK9后能与CMS搭配的新生代回收器只有ParNew了;

三、Parallel Scavenge(STW)

  与ParNew一致,Parallel Scavenge是基于标记-复制算法的多线程的新生代垃圾回收器,但是其关注的是吞吐量优先;吞吐量高意味着垃圾收集的时间短,而要确保垃圾收集时间短,在回收策略优化调整的基础上也会不可避免的会使垃圾回收的次数增加,而垃圾回收次数增加又会造成用户代码的停顿次数变多,这就要看用户根据实际情况取舍了,一般针对cpu密集型的程序更关注吞吐量,而io密集型的就需要去平衡下吞吐量和用户代码停顿时间的关系了;

  • 吞吐量
    吞吐量=执行用户线程的时间/(执行用户线程的时间+垃圾收集的时间)
  • Parallel Scavenge的常用参数
    -XX:MaxGCPauseMills;指定垃圾收集能停顿的最大时间,单位毫秒,收集器将尽量保证垃圾回收的时间不超过该设定的时间;
    -XX:GCTimeRatio;设置吞吐量大小,取值范围为0-100
    -XX:+UseAdaptiveSizePolicy;开关参数;开启后就不用用户去指定新生代的大小(-Xmn)、Eden与Survivor的比例(-XX:SurvivorRatio)等参数了,虚拟机会根据当前系统的运行情况自动收集性能监控信息,动态调整这些参数以达到最合适的停顿时间或吞吐量,这种调节方式称为垃圾收集的自适应调节;

老年代垃圾回收器

一、Serial Old(STW)

  是Serial收集器的老年代版本,负责回收老年代,同意也是单线程GC,由于回收的是老年代,没有其他的空积做担保,所以采用的是标记-整理算法

二、Parallel Old(STW)

  多线程的基于标记-整理算法的老年代垃圾回收器;是Parallel Scavenge的老年代版本;PS+PO的搭配也是JDK8的默认垃圾收集器的搭配;

三、CMS

  是一款关注用户响应时间的老年代垃圾回收器,其在回收中的某些阶段做到了不用暂停用户线程,这在垃圾收集器的历史上具有里程碑的意义,但是其在触发FULLGC后清理老年代的回收器是Serial,单线程的垃圾回收器,显而易见的慢;另外CMS还是唯一一款会单独回收老年代(Old GC)的垃圾回收器,下面介绍下CMS的回收过程以及优缺点

  • 回收过程
    1、初始标记(STW)
    只标记gcroots可直接关联的对象,时间会很快;
    2、并发标记
    标记那些需要被回收的对象,是与用户线程一起运行的,所以有可能会产生漏标的情况发生,CMS使用的是三色标记算法的增量更新算法来解决这一问题,算法详情可参考上篇文章;
    3、重新标记(STW)
    针对并发标记阶段用户线程产生的新垃圾进行重新标记
    4、并发回收
    开始回收垃圾,因为同样是与用户线程一起运行的,所以用户线程会产生新的垃圾,这些垃圾被称为浮动垃圾,只能下次清理时处理;
  • 优点
    支持垃圾线程和用户线程并发执行,减少了用户的等待时间
  • 缺点
    结合回收过程CMS的缺点也是显而易见的,大致可分为以下三种:
    1、由于最后回收阶段采用的是并发回收,用户线程产生的垃圾是必须要到下次GC才会被回收,会产生浮动垃圾;
    2、并发回收阶段采用的是标记-清除算法,这样容易产生内存碎片,导致内存不规整,有可能分配不下大对象而产生FULLGC,而上面也说过了,FULLGC时CMS采用的是Serial
    3、CMS并发回收阶段使用的线程数是通过(CPU核心数+3)/4计算而来,这样一来,CPU核数大于4还好,如果小于4的话那就意味着最差需要用一半的线程来进行垃圾回收,回影响系统的吞吐量

全堆垃圾收集器

G1

  G1是一款面向全推进行回收的可以与用户线程一起进行的垃圾回收器;其在CMS的基础上做了一定程度的优化,但成本就是堆内需要使用部分内存来供G1本身使用;与上面介绍的一堆垃圾收集器相比,G1只在逻辑上进行了分代,物理上比没有划定哪些区域是新生代或老年代;G1还推出了时间响应模型即在给定的M毫秒时间内用于垃圾回收的时间不超过N毫秒,这一实现主要依赖于collection set(CSet)来实现;下面介绍下G1的回收模型和优缺点

回收模型

minorGC

新生代的初始大小有个默认值,约占堆空间的5%-60%,由G1根据需要动态调整;当达到设置的阈值时进行YGC

MixedGC
  • 定义
    G1与上面介绍的其他收集器不同,不用将新老年代限制死,回收时要么回收新生代(minorGC),要么老年代(majorGC)要么全堆收集(FullGC),G1可以面向堆内存的任何部分来组成回收集(Cset)进行回收;衡量标准不再是垃圾属于哪个分代,而是哪块内存垃圾多回收收益最大,这就是G1的MixGc模式;MixedGC也会有个触发的默认值.超过该值进行GC,默认应该是堆空间的45%
  • 回收过程
    1、初始标记:和CMS的初始标记一样只标记GC Roots能直接关联到的对象,并修改TAMS指针的值,让下一阶段用户线程并发运行时能正确地在可用的region中分配对象;需要暂停用户线程,但耗时很短,且是借用Minor GC的时候同步完成的,所以并没有额外的停顿
    2、并发标记:从GC Root开始对堆中对象进行可达性分析,找出要回收的对象,耗时较长所以可以与用户线程并发执行;扫描完后会维护SATB记录下来的并发时由用户线程造成的引用发生改变的对象(三色标记的SATB算法)
    3、最终标记:暂停用户线程,处理并发标记阶段SATB中维护的引用发生改变的记录
    4、筛选回收:暂停用户线程,对各region的回收价值和回收成本进行排序,根据用户期望的停顿时间来制定回收计划,可以选择任意个region构成回收集,然后把需要回收的region中的存货对象复制到空的region中,再清理掉region的全部空间;由多条GC线程并行完成上述操作
FullGC

当堆空间不足以分配新对象时触发FGC

优点

1、全堆垃圾收集器;
2、仅进行了逻辑上的分代,物理上并不分代;
3、逻辑上保留了新老年代的概念,但新老年代的位置并不固定,它们是一系列区域(不需要连续)的动态集合;
4、Region为G1的最小回收单元;可由-XX:G1HeapRegionSize设定每个region的大小,取值范围为1-32MB,且应为2的N次幂
5、利用停顿时间模型用户可以指定垃圾收集的停顿时间,G1也会尽量满足这个设定的时间

缺点

1、为了垃圾收集而产生的内存占用和程序运行时的额外执行负载都比CMS要高
2、G1每个region中维护的记忆集(Rset)可能会占整个堆容量的20%甚至更多;而CMS使用的是卡表,全堆只需维护一份,且只需记录老年代到新生代的引用;

collections set(cSet)

维护了需要下次垃圾回收的region的集合,并且根据回收价值进行了排序,这样筛选回收时就能根据系统默认或者用户设定的响应时间优先回收价值高的region

remember set(rSet)

每个region维护一个rSet,用来记录别的对象到本region的引用,用rSet就是为了解决垃圾回收时对象跨代引用的扫描问题,这样操作大大节省了GC回收时扫描的时间;但是随之而来的问题是,维护rSet也是需要一定内存空间的;

原文地址:https://www.cnblogs.com/darling2047/p/14631675.html