垃圾回收算法与垃圾回收器详解

一、垃圾回收算法

一般对jvm的垃圾回收算法都是基于分代收集思想实现的,基于jvm的堆内存分代的划分,一般根据对象在堆中的存活周期,划分为年轻代和老年代,基于不同代的特点选择最合适的垃圾回收算法,一般有三种算法:

下面仔细分析下各种算法

1.标记-复制

标记-复制算法是通过标记出非垃圾对象(存活对象),并且把存活对象复制到堆中另外一块空白内存中,然后把原来的内存空间全部回收掉。一般用于年轻代

 标记复制算法一般把内存分为大小相等的两块,一块用于分配对象,一块作为保留内存空间(暂时不使用)。当使用的那块空间满了之后,经过GC 把存活对象复制到保留内存空间中,然后清空那块内存空间。如此反复。

优点

       1.实现简单,逻辑简单易理解。

        2.效率高,在存活对象较少的时候,因为需要复制和标记的对象较少,所以效率很高。因此这个算法一般比较适用于存活对象较少的地方,比如年轻代,因为年轻代几乎都是朝生夕死的对象,每次GC能存活下来的对象占少数,所以适用标记-复制算法效率较高。

缺点:

       1.浪费内存空间,因为该算法需要两块内存空间,而且长时间有一块内存空间是不能使用的(只能作为保留内存空间用于复制存活对象时使用)。

        2.因为挪动了对象在内存中的位置 所以需要更新地址引用,也是影响效率的。

2.标记-清除

标记-清除算法通过标记出非垃圾对象,然后直接清除未被标记的垃圾对象。(也可以反过来标记垃圾对象  清除标记过的对象  一般不用这种方式)

这种算法也是比较简单的,但是也会带来问题:

缺点:

       1.效率问题,标记的对象很多时,影响GC效率

       2.碎片空间,GC后会产生大量的空间碎片,可能出现如果来了一个大点对象,无法找到连续的内存分配给它。

优点:

       1.因为不用移动存活的对象,不用更新对象的地址引用

        2.相比于复制算法,不会浪费内存空间

3.标记-整理

标记整理算法分为三步:标记,清除,整理三部分,其中标记与标记-清除算法一样,只不过在后面稍微有点不同,标记完存活对象后,会把存活对象向一端移动,把垃圾对象向另一端移动,移动完之后,把另一端的垃圾对象全部回收掉。

这种算法可以解决标记-清除算法产生的碎片空间问题 

优点:

       1.不会产生碎片空间

       2.对比复制算法 不会浪费内存空间
缺点:

       1.标记对象太多效率不高

       2.对象挪动,需要更新对象地址引用

二、垃圾收集器

随着应用对GC效率的要求的越来越高,市场出现了各种各样的垃圾收集器:

 1.Serial(-XX:UseSerialGC 年轻代 -XX:UseSerialOldGC 老年代)

是一个串行收集器,使用单线程收集,在进行垃圾收集时,使用单线程收集垃圾并且在回收垃圾时会触发STW(stop the world)暂停java中所有的用户线程,直到收集结束。年轻代:标记-复制算法老年代:标记-整理算法

在Serial收集器收集时使用单线程收集,所以收集所需要的时间会比较长,导致STW的时间相对较长,但是也是因为单线程这个特性,可以不用进行多线程的切换和交互,保证了单线程的回收效率最高。

Serial Old是Serial老年代的版本,用于老年代的来及收集,而且当CMS(另一个垃圾收集器 下面详解)在并发收集出现问题时,作为后备的收集方案使用。

2.Parallel(-XX:UseParallelGC 年轻代  -XX:UseParallelOldGC 老年代)

Parallel收集器,基于Serial收集器的一个改进,由于Serial收集器使用单线程收集,导致STW时间过长,效率不高,Parallel收集器采用多线程收集,可以在GC时,大幅度减少STW的时间,提升用户的体验。

多线程并发收集,提高了整体的收集效率。年轻代采用标记-复制算法,老年代采用标记-整理算法

Parallel Old是老年代版本,用于老年代的收集。JDK8默认的年轻代和老年代的收集器。

3.ParNew(-XX:UseParNewGC 年轻代)

ParNew和Parallel收集器类似,唯一的区别在于ParNew可以配合CMS(老年代的一款收集器)收集器使用,而Parallel不可以。

ParNew只用于年轻代的收集,不可用于老年代,所以往往配合CMS一起使用。

使用标记-复制算法

4.CMS(-XX:UseConcMarkSweepGC 老年代)

CMS收集器,全称 Concurrent Mark Sweep 是一款真正意义上的并发收集器,为了解决垃圾收集时停止java用户线程时间过长导致的槽糕的用户体验,CMS采用分段多次+并发标记的思想,使得一次长时间的STW分为多段短时间的STW,这可以有效的缓解一次STW时间很长的缺点。基本上实现了让垃圾回收线程与应用线程同时工作

CMS收集主要分为五个步骤:

  • 初始标记:暂停当前所用应用线程,然后从GC Roots开始标记,本次标记只标记直接引用的对象,效率高,速度很快。
  • 并发标记:这个阶段就是开始从GC Roots开始标记整个对象引用链路,不在是直接引用了,耗时较长,效率不高,但是并不用停止用户线程,对用户影响不大。但是并发标记会导致,标记过程中 对象的状态发生改变,例如 GC线程刚标记完一个对象为非垃圾对象,此时用户线程删除了对象的引用,导致其成为垃圾对象了,反之亦然,这就会存在并发标记的不确定性。
  • 重新标记:这个阶段是需要停止所有用户线程的,然后对于上一个阶段标记的结果进行修正,也就是对那些状态发生变化的对象重新标记。这个阶段的STW时间比初始标记要长,但是远远低于并发标记的耗时。这一阶段主要使用了三色标记中的增量更新算法进行重新标记。
  • 并发清理:这个阶段主要对未标记的垃圾对象进行并发清理的,不用停止用户线程,所以对象用户体验影响不大。但是并发清理也会存在对象状态在清理期间发生变化的情况,主要分类两种情况:漏标多标(下面会对此说明)。在并发清理的阶段如果产生了新的对象,该对象被直接标记为黑色,这样就不会多回收存活的对象的问题了。
  • 并发重置:重置本次GC过程中的标记数据。

从这几个阶段我们很明显感受到和其他几款收集器的不同,

优点:可以并发收集,用户线程停顿时间(STW)低。

缺点也很明显:

1.对CPU资源敏感,可能会和用户线程抢夺资源。

2.在并发标记和清理时,对象状态发生变化,非垃圾对象变为垃圾对象,而我们确标记了,本次未回收掉,产生浮动垃圾。

3.它使用的是标记-清除算法,因此会产生大量的碎片空间。不过可以通过设置参数(-XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理)减少碎片。

4.在本次GC还未完成时,由于用户线程继续运行产生新的垃圾,导致又一次触发full GC了,本次回收失败(concurrent mode failure)那么CMS只有停止所有用户线程 ,然后使用Serial Old收集器进行收集。因此Serial Old收集还有作为CMS预备收集器的作用。一旦产生这种情况,并发收集就转为单线程收集,回收效率低下,停顿时间长,所以尽量要避免发生这样的情况。可以通过设置-XX:CMSInitiatingOccupancyFraction(当老年代达到这个值是触发CMS回收)参数尽可能的避免发生这中情况。

CMS的核心参数:

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

扩展:三色标记法

三色标记 在并发标记的过程中 ,因为标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。
这里我们引入“ 三色标记 ”来给大家解释下,把Gcroots可达性分析遍历对象过程中遇到的对象, 按照“是否访问过”这个条件标记成以
下三种颜色:
黑色 : 表示对象已经被垃圾收集器访问过, 且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描
过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过
灰色对象) 指向某个白色对象。
灰色 : 表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
白色 : 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若
在分析结束的阶段, 仍然是白色的对象, 即代表不可达。
 
在CMS并发标记和并发清理阶段会产生 多标和漏标的情况。
多标——产生浮动垃圾。
 

并发阶段 因为有用户线程的运行,可能会导致之前标记的存活对象变成了垃圾对象,在回收时,这个对象还是被标记为存活对象,所以CMS并不会回收它(其实此时它已经是垃圾对象了),这种垃圾对象就是浮动垃圾。对于这种情况CMS并不会去处理它,而是等待下一次的GC在清理。

漏标——可能导致回收存活的对象(CMS对此有两种解决方法)

场景:并发清理时,有心的对象产生,此时新产生的对象并没有被标记,在回收时会回收掉所有未被标记的对象(新生对象也在此列),这就是漏标会导致的问题。

两种解决方案:增量更新(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB)

增量更新: 就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之
后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向
白色对象的引用之后, 它就变回灰色对象了(CMS重新标记阶段使用了这种方式)
 
原始快照: 就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后,
再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑
色( 目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾 )
以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过 写屏障 实现的。
 
后续还有G1收集器,不过G1收集器的逻辑比较复杂 准备单列一章进行记录。
 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值