垃圾收集器ParNew和CMS与底层三色标记详解

1、垃圾收集算法

大体的垃圾收集算法分为三种,还有一种垃圾收集算法的理念,用图解的方式表达如下:
在这里插入图片描述

1.1、分代收集理论

分代收集理论:根据不同代的特点选择对应的垃圾收集算法,主要特点就是年轻代和老年代中的对象存活周期不太一样。

1.2、标记复制算法

标记复制算法:将一块内存区域分为大小完全相等的两块,假设就好比下图的A区和B区,先使用A区,使用完做gc时就将A区的所有存活对象标记复制整理到B区,将A区全部回收,接下来使用B区,这时候就相当于A、B区调换。
     优点:效率较高。
     缺点:浪费内存,内存只有一半的使用率。

在这里插入图片描述

1.3、标记清除算法

标记清除算法:算法有两步,分为标记和清除,一种是标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,详情请参考下图:
     优点:内存使用率100%。
     缺点1:效率不高,如果存活的对象比较多,要标记的对象太多,就会产生效率问题。
     缺点2:会产生大量的内存碎片,导致有些大对象放不进来。

在这里插入图片描述

1.4、标记整理算法

标记整理算法:根据老年代的特点推出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存,详情请参考下图:
     优点:整理之后的内存地址非常的工整。
     缺点:花费的时间较长。

在这里插入图片描述

2、垃圾收集器

垃圾收集器就是内存回收的一个具体实现,现在Java有很多垃圾收集器,为什么要写那么多垃圾收集器,是因为到目前为止,没有一种垃圾收集器是适用于所有场景的,所以才出现了这么多版本的垃圾收集器,在不同的应用场景下选择合适的垃圾收集器,详情见如下图所示:

在这里插入图片描述

2.1、Serial垃圾收集器

分为两个,年轻代和老年代都有,使用参数如下:
-XX:+UseSerialGC -XX:+UseSerialOldGC
单线程的一种垃圾收集器,在计算机单核的时候效率会比较高,在回收内存时会进行STW(Stop the World),详情如下图所示:
特点:简单,易懂,适用于单核CPU。

在这里插入图片描述

2.2、Parallel Scavenge垃圾收集器

JDK1.8的默认垃圾收集器
分为两个,年轻代和老年代都有,使用参数如下:
-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
Parallel是Serial的多线程版本,默认的收集线程数跟CPU的核数相等,可以通过(-XX:ParallelGCThreads)修改,但是一般不推荐修改,详情请看下图:
特点:Parallel Scavenge垃圾收集器关注的是吞吐量(CPU利用效率高,垃圾收集时间短),所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。

在这里插入图片描述

2.3、ParNew垃圾收集器

由于后面出现的CMS收集器是专门收集老年代的,而年轻代的Parallel不能与CMS联合使用,所以在Parallel进行了一些改动,产生了ParNew,使得和CMS有了一个联合使用,ParNew回收年轻代,CMS收集老年代,详情看下图:
开启ParNew:-XX:+UseParNewGC
特点:除了Serial收集器,只有ParNew能与CMS合作收集,且是多线程的效率很高。

在这里插入图片描述

2.4、CMS垃圾收集器

CMS(Concurrent Mark Sweep)并行标记清除,垃圾收集线程与用户线程同时运行,以减少STW时间为目标的收集器,非常注重用户体验,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
开启CMS:-XX:+UseConcMarkSweepGC
CMS垃圾收集器主要的步骤如下:
初始标记:先STW,并记录下gc roots直接引用的对象,速度很快,如果不做STW,gc root会非常多。
并发标记:根据初始标记的结果,做整个的一个可达性分析,找出所有的被引用的对象,这个过程耗时比较长(大约占整个收集过程的80%左右),但是这个过程和用户线程并发执行,所以用户无感知,但是因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变(就比如在并发标记前是非垃圾,标记之后是垃圾或者并发标记前是垃圾,并发标记后变非垃圾),详情看本篇下面的三色标记处理。
重新标记:先STW,同时修复在并发标记里面出现状态变换的对象,主要用到三色标记里的增量更新算法(见下面详解)做重新标记,详情见下面的三色标记。
并发处理:将垃圾对象一次性清除掉,这里涉及到一个对新增数据对象的处理,用三色标记,详情见下面的三色标记。
并发重置:重置本次GC过程中的所有标记数据。

在这里插入图片描述

CMS优点:并发收集、低停顿。
CMS缺点:
     1、对CPU资源敏感,用户线程和垃圾收集线程会争抢资源,会对垃圾收集的吞吐量造成影响。
     2、无法处理浮动垃圾,在并发清理的过程中从非垃圾对象编程垃圾对象的对象就是浮动垃圾,这种浮动垃圾会在下一次gc的时候被清理掉。
     3、因为使用的是标记–清除算法,所以会产生大量的内存碎片,但是我们可以通过设置-XX:+UseCMSCompactAtFullCollection参数来让每次做完CMS之后做一次标记–整理来消除内存碎片。
     4、concurrentmode failure(并发失败),在CMS执行并发的过程中,由于用户线程还在执行,万一丢进来的大对象老年代放不下,这个时候并发过程进行不下去了,这个时候就产生了并发失败,接下来不会OOM,而是会STW,用serial old垃圾收集器来回收(因为只有Serial和ParNew可以和CMS配合使用),然后就相当慢,给用户的感觉就是网页卡住了,解决方案是配置-XX:CMSInitiatingOccupancyFraction参数,详情请看下面辅助知识的CMS核心参数。

3、垃圾收集底层实现算法

3.1、三色标记

在并发标记过程中,由于用户线程没有停止运行,所以就会产生多标、漏标或少标的情况
多标:就是在并发标记之前是非垃圾对象,并发标记做完之后成为了垃圾对象,容易产生浮动垃圾,这种情况还不怎么影响,浮动垃圾在下一次垃圾回收的时候直接回收就好了。
漏标或少标:就是在并发标记之前是垃圾对象,并发标记做完之后成为了非垃圾对象,这里为了防止漏标对象被删除,引入了三色标记算法(只是人为为了理解底层起的一个名字)。

三色标记释义:三色标记是在gc root可达性分析时,对所经过所有对象进行一个颜色标记,分为三种颜色。
黑色:表示该对象被垃圾收集器扫描过,且该对象的所有引用也都被扫描到,标记为黑色,后续在做垃圾回收时,不会对黑色对象做任何处理。
灰色:表示该对象被垃圾收集器扫描过,但其所引用的对象至少还有一个未被扫描到,标记为灰色。
白色:表示该对象未被垃圾收集器扫描过,标记为白色,白色是所有的对象的初始化标记,表示不可达。

3.1.1、多标–浮动垃圾

在并发标记的时候,一个对象从非垃圾对象变成了垃圾对象,这就称为浮动垃圾,浮动垃圾不会被gc清除掉,但是由于浮动垃圾不会影响到gc的运算过程,所以在下一次gc时回收掉即可,另外,针对并发标记以及并发清理开始后产生的对象,通常的做法是直接当成黑色,本轮gc不会对其进行清除,这类对象也可能成为垃圾,也算是浮动垃圾的一种。

3.1.2、漏标–读写屏障

漏标是指在并发标记前对象为垃圾对象,但在并发标记之后又成为了非垃圾对象,会产生一种在并发清理时被误删的bug,所以产生了一下两种解决方案:
增量更新(IncrementalUpdate):在并发标记的过程中,所有赋值对象的操作都会被JVM给记录下来放在一个集合里(这是初始标记之后,在并发标记中产生的一些操作,后续在重新标记时需要对他们进行重新扫描),通过后面的重新标记过程将所有被赋值(例如下面代码的a就是被赋值对象)的对象全部置为灰色,灰色对象是未被扫描完成的,所以会被继续扫描,这个时候,在并发标记时做了赋值被引用的对象例如下面代码的d就是被引用对象)就会被标记成灰色,在之后的并发处理时就不会被回收掉。

// 假设a是gc root,d在并发标记之前是垃圾对象
// 这行代码是在并发标记之后做的
a = d;

原始快照(Snapshot At The Beginning,SATB):在并发标记时,会将所有的赋值null操作的原始快照(也就是被赋值为空的对象的原始引用的对象)放到一个集合里面,在重新标记的时候,会将集合里面所有被抛弃的对象全部赋值为黑色,这样gc就不会回收,就算并发标记结束之后,它依然是垃圾,这个垃圾只会变成浮动垃圾,在下一次gc的时候会回收,详情请看下面代码。

a = d;
// 这里开始做并发标记
// 会在a = null;之前做一个写屏障,将a之前引用的d放在一个集合里
// 将d直接标为黑色
a = null;

3.2、写屏障

所谓写屏障就相当于是在一个写操作执行的前后执行一些方法,优点类似于Spring里面的AOP操作。
增量更新:因为是增加了引用之后,所以一般就会在写操作之后,执行一个类似于下面代码的end_write方法,异步的将指针放到一个队列里面(为了提升性能,不要影响正常的写操作),后台会有一个专门的线程去维护这个队列,并将新引用的对象放在集合中。
原始快照:原始快照,说的是写之前被引用的对象需要被保存在集合里,所以会执行类似于下面代码中begin_write方法去操作,异步的将指针放到一个队列里面(为了提升性能,不要影响正常的写操作),后台会有一个专门的线程去维护这个队列,并将新引用的对象放在集合中。

void write_field_barrier(oop* field, oop new_value) {  
    begin_write(field);          // 写前屏障操作
    *field = new_value; 
    end_write(field, value);  	// 写后屏障操作
}

10、辅助知识

10.1、Parallel和CMS的区别

Parallel主要关注吞吐量,当单位时间内收集的垃圾越多越好,CMS主要关注点在于用户体验,它在相同大小内存的情况下可能做一次GC的时间比Parallel还要长,可是在总体STW(因为STW可以让用户感到停顿)的时间却要短了很多,让用户的体验感觉上升了不少,所以,一般在较大的内存时采用CMS,比如4G到8G差不多。

10.2、CMS核心参数

参数含义
-XX:+UseConcMarkSweepGC启用cms
-XX:ConcGCThreads并发的GC线程数
-XX:+UseCMSCompactAtFullCollectionfull gc之后做压缩整理,减少内存碎片
-XX:CMSFullGCsBeforeCompaction多少次full gc之后做一次整理,默认是0,代表每次full gc完都会整理
-XX:CMSInitiatingOccupancyFraction当老年代的空间使用占比达到这个比例就会触发full gc,默认使92%
-XX:+UseCMSInitiatingOccupancyOnlyfull gc时只使用-XX:CMSInitiatingOccupancyFraction指定的值,如果不配置,JVM会根据堆的使用情况,自己做一些微调,所以一般不建议设置这个参数
-XX:+CMSScavengeBeforeRemark在触发full gc之前执行一次minor gc,目的是减少老年代对年轻代的跨代引用,因为跨代引用就要做并发标记,并发标记的时间占80%左右,比较费时
-XX:+CMSParallellnitialMarkEnabled表示在初始标记的时候多线程执行,缩短STW
-XX:+CMSParallelRemarkEnabled在重新标记的时候多线程执行,缩短STW

10.3、记忆集和卡表

10.3.1、记忆集(Remember Set)

记忆集主要解决跨代引用,我们在做minor gc时,有些年轻代的对象会被老年代所引用,这个时候我们就不能回收这些对象,要解决这个问题的话,我们就得去对整个老年代的对象做可达性分析,效率非常的低下,这个时候就有了记忆集的出现,记忆集将我们所有老年代引用年轻代的对象存在集合里,在我们minor gc在做可达性分析时,随记忆集里面老年代的对象也做一次可达性分析,这样,我们的跨代引用对象就会被扫描到,就不会被回收。
注意:事实上并不只是新生代、 老年代之间才有跨代引用的问题, 所有涉及部分区域收集(Partial GC) 行为的垃圾收集器, 典型的如G1、 ZGC和Shenandoah收集器, 都会面临相同的问题。我们无需知道非回收代里面的具体细节,只需知道有谁引用着即可。

10.3.2、卡表(cardtable)

卡表是记忆集的一个实现,卡表与记忆集的关系相当于Java里面haspmap与map的关系,卡表相当于将我们的老年代转化成一个存放card的表,老年代被划分成相等区域的若干块,每一块区域被称之为一个card,用一个数组CARD_TABLE[ ]将这些card引用起来,如果某一个card中有对象跨代引用,将被判断为dirty(脏),其对应的元素表示被置为1,并且记录下它的内存地址,以便于做可达性分析,jdk1.8的hotspot源码中每个card是512字节,维护卡表也是通过写屏障实现的,详情如下图:

在这里插入图片描述

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值