【垃圾收集器与底层算法实现】

一、垃圾收集算法

1.1 标记复制算法

这个算法在对垃圾进行回收时,主要体现在两个过程,分别是:标记、复制,也就是标记不是垃圾的对象,会事先把内存分为两块,每次标记完毕之后,会将标记对象复制到另外一个内存区域,其他的对象则是全部清除,具体的过程我们可以看如下图示:
整理前
在这里插入图片描述
整理后
在这里插入图片描述

1.2 标记清除算法

相比标记复制算法,这个算法不用讲内存分开,是在现有的内存空间进行标记=>清除,标记是标记存活的对象,清除是清除掉没有被标记的对象,具体如下图示:
整理前
在这里插入图片描述
整理后
在这里插入图片描述
由上图可知,这种垃圾回收算法会产生两个问题,一个是在如果标记的对象较多,效率比较慢,第二个是会产生大量不连续的内存碎片

1.3 标记整理算法

这种算法其实对第二种产生的碎片问题进行优化的,因为在清除时,会产生大量不连续的碎片内存,那这个算法就是将这些碎片的内存进行整理,具体如下图示:
整理前
在这里插入图片描述
整理后
在这里插入图片描述

二、垃圾收集器

2.1、垃圾收集器—Serial&Serial Old

这是收集器中最基本的,他是单线程进行回收的,新生代用的垃圾回收算法是标记复制、老年代是标记整理算法,在对垃圾进行回收时,会先暂停其他所有的工作线程(Stop The World),一直到收集结束后,具体可见如下图示:
在这里插入图片描述
单线程好处就在于没有线程之间的切换,减少了一定的开销

2.2、垃圾收集器—Parallel&Parallel Old

这个其实就针对Serial存在的单线程较慢缺陷的基础上,添加了多个线程进行垃圾回收,所以也叫serial收集器的多线程版本,所以,新生代和老年代用到的垃圾回收算法和Serial是一样的,新生代用的垃圾回收算法是标记复制、老年代是标记整理算法,这个收集器关注点是吞吐量(高效的利用CPU),所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值,具体运行机制我们可以查看下图图示:
在这里插入图片描述

2.3、垃圾收集器—ParNew&CMS

2.3.1 ParNew回收器

这个回收器和Parallel是一样的,主要是为了配合CMS一起使用的,具体结构如下图示:
在这里插入图片描述

2.3.2 CMS回收器

CMS(Concurrent Mark Sweep)这种回收器是一种以获得最短回收停顿时间为目标的收集器,非常符合在注重用户体现的应用上使用,是HotSpot虚拟机第一款真正意义上的并发收集器,是第一次实现让垃圾收集线程与用户线程同时工作:,它是一种基于标记-清除算法实现的,其步骤主要有五步:

  1. 初始标记:这个过程会STW,这个阶段会记录所有直接被GCRoots引用的对象,速度很快
  2. 并发标记:这个阶段就是从GCRoots的直接关联的对象开始遍历整个对象图的过程,这个过程很耗时,但是这个过程不需要停顿用户线程,可以与垃圾收集线程一起并发,因为用户线程同时运行的,可能会导致已标记过的对象状态会发生变化:
  3. 重新标记:重新标记阶段是为了修正并发标记期间因为用户线程继续运行而导致标记产生变动的那部分对象的标记记录,这个阶段的停顿时间比初始标记的时间稍微长一点,但是远远比并发标记阶段的时间短,主要是用到三色标记的增量更新算法做重新标记
  4. 并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫,这个阶段如果有新增的对象会被标记为黑色,不做任何处理
  5. 并发重置:重置本次GC过程中标记的数据

具体我们可以查看如下图示:
在这里插入图片描述
其主要的优点在于:并发收集、低停顿,但是回收过程也会产生一些缺陷,例如:无法处理浮动垃圾,除此之外,因为是由标记-清除算法实现的,那收集完毕之后,会产生大量的内存空间碎片,其实这个也可以通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理,但是:执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收

CMS核心参数配置:

  • -XX:+UseConcMarkSweepGC:启用CMS;
  • XX:ConcGCThreads:并发的GC线程数;
  • XX:UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片);
  • XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次;
  • XX:CMSInItiatingQccupancyFraction:当老年代使用达到配置这个比例时,会触发FullGC(默认是92%);
  • XX:+UseCMSInitiatingQccupancyOnly:只使用设定的回收阈值,如果不指定,JVM仅在第一次使用设定值,后续则会自动调整;
  • XX:CMSScavengeBeforeRemark:在CMS GC前启动一次minor GC,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段性开销,一般CMS的GC耗时80%都在标记阶段;
  • XX:CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW;
  • XX:CMSPARAllelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

三、垃圾收集底层算法实现—三色标记

3.1、三色标记

在并发标记过程做,因为标记期间,应用线程还在跑,对象间的引用可能会发生变化,多标或者漏标的情况就有可能发生,所以这里就引入了三色标记来解决上述问题,这里是把GC Roots可达性分析遍历对象过程中遇到的对象,按照当前遇到的对象是否被访问过分为三种颜色:

  1. 黑色:表示当前遍历到的对象被垃圾回收器访问过,且这个对象的所有引用都已经扫描过,黑色的对象代表它是安全的,是存活的对象;
  2. 灰色:表示对象已经被垃圾回收器扫描过了,但这个对象上至少存在一个引用还没有被扫描过;
  3. 白色:表示当前对象没有被垃圾回收器扫描过,这种对象如果是在分析完毕之后还是白色,那这种对象就代表不可达,是要被回收器当作垃圾回收的;

我们可以看下图示:
在这里插入图片描述
在这个分析的过程中,会出现==>当方法运行结束后,方法中的对象就会被释放了,但是这个对象在之前的标记过程中,被标记为非垃圾对象,那这时,这个对象其实是没用的了,只能在下一次扫描回收时,才被扫描回收,这种我们称为浮动垃圾

还有就是漏标,这种是把 还有应用对象当作垃圾给清除了,这种情况的解决方案主要有两种,分别是增量更新和原始快照

  • 增量更新:就是说如果当前被标记的黑色对象,中途插入了一个对象引用,那这时候,这个操作就会被记录下来,等并发扫描结束后,再将这些记录过的引用关系中,黑色对象为根,重新再扫描一次,简单来说,就是黑色对象一旦在扫描完成后,会将当前黑色对象变为灰色;
  • 原始快照:这个就是将当前灰色对象要删除指向的白色对象时,会将这个要删除的引用记录下来,在并发标记结束之后,在将这些记录中,灰色对象作为根,重新扫描一次,若扫描到白色对象,就将白色对象置为黑色,保证这种对象能在本次回收中存活下来;

实现上述的原理,底层用到的是写屏障:所谓的写屏障,其实就是指在赋值操作前后,加入一些处理(可以参考AOP的概念)

写屏障实现STAB、增量更新:主要是在删除/修改指向的白色对象之前,会将原先的引用记录下来,而增量更新是赋值完毕之后,会将这个赋值的引用记录下来

四、小结

这节我们是先从垃圾的回收算法=>垃圾回收器=>垃圾回收器底层算法实现展开学习的,我们了解到垃圾回收算法和垃圾回收器的循序渐进的升级,都是针对前一代垃圾回收器/算法遗留或者发现的问题做改善,不断的优化的过程,而且也对三色标记中,针对CMS垃圾回收器在并发标记阶段产生漏标情况给予解决方案=>增量更新和原始快照,以及这两种解决方式的底层原理=>读写屏障来保证(类似AOP机制)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值