垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)

开门见山,常见的GC(垃圾回收)算法有以下四种,本文重在解析GC算法,是为讲解垃圾回收器做知识铺垫。至于分代回收将在其它文章中补充说明。

目录:

1. 引用计数法(Java没有采用)

2. 标记-清除法 (jvm老年代回收)

3. 标记-压缩法 (jvm老年代回收)

4. 复制算法 (jvm新生代回收)

5. 几种算法对比

至于新生代和老年代的说法会在本文第4-5点简要介绍

1. 引用计数法(Java没有采用)

每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,但无法解决对象相互循环引用的问题,如下图所示。

①当出现左边绿色的情况时,假设外部对A的引用消除,此时因为A引用计数从1减为0,A将被清除。从而对B的引用也消除,B的计数减为0,GC将正确回收对象{A, B}

②然而若出现右边橙色状况,假设外部对E的引用消除,外部对于对象集{C,D,E}不再有引用,但他们之间出现循环引用现象,计数始终保持为1,导致{C,D,E}无法被回收。

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第1张图片

2. 标记-清除法

(这部分是我在课程展示时做的ppt讲的,在原ppt上有动画和伪代码对比演示,以及如何对扫描整个堆的标记-清除法作出优化,想要扩展的可以点击链接下载)

标记清扫回收器ppt演示(含动画动态展示) 

标记-清扫式垃圾回收器是一种直接的全面停顿算法。简单的说,它们找出所有不可达的对象,并将它们放入空闲列表Free。

清扫过程将分为标记阶段和清扫阶段。以下是结合伪代码的详细分析。

①首先把根集所引用的对象reached(可达标志位)设为1,这里假设根集引用了o1和o4,并将其加入到未扫描集合Unscanned中。至于根集是一组必须活跃的引用,比如所有活跃栈帧里指向GC堆里对象的引用(即正在被调用的引用类型参数和局部变量等)。

    对于根集的更多详情可查看引文:对于根集的理解

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第2张图片

② 删除未扫描集合中的某个对象o1,并找到o1所引用的对象o3和o8;(原书写的删除对象并找到引用,我认为是先找到引用再删除,可能有翻译上的误差)

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第3张图片

  再将o3和o8的reached标志位设为1,并将o3和o8放入未扫描集合中;

(未扫描集合中的对象表示是可达的,它们都可以被直接或间接引用)

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第4张图片

③和以上步骤类似,此时删除未扫描集合中的引用对象如o4,找出o4所引用的对象,改变被引用对象的标志位为1并放入未扫描集合。重复以上操作,直到Unsanned集合为空,同时所有直接或间接引用的对象的 reached 位都被标记为1,进入清扫阶段;

④清扫所有 reached 标志位为0的对象(没有被直接或间接访问过),被清扫对象放入Free集合中;

⑤将存活的对象 reached 标志位归还位0,进行下一轮标记-清扫工作。
 垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第5张图片

清理效果如图所示:

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第6张图片

缺点:需要扫描整个堆区,时间开销较大。感兴趣的可以查看我上传的ppt中,标记-清除法的优化版。

 

3. 标记-压缩法

总体思想和标记-清除法类似

①标记阶段,通过根节点标记所有可达(直接或间接可访问)对象,和标记-清除法类似;

②清除阶段,将上一轮存活对象压缩到内存的一端,之后清理边界。(如此一来可以减少内存碎片,避免分配大对象时空间不够)

清理效果如图所示!

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第7张图片

 

4. 复制算法 

在堆区中,对象分为新生代(年轻代)、老年代和永生代,而复制算法发生是发生在新生代的。新建的对象一般分配在新生代的Eden区,当Eden快满时进行一次小型的垃圾回收。存活的对象会移动到 Survivor1区(以下简称S1)。

当再次发生 GC 时,S1区的存活对象将复制到先前闲置的S2区,同时存活对象寿命+1;以后每次发生GC,S1和S2区将交替的作为存活对象的存放区和闲置区。并且如果存活对象的寿命达到某个阈值,它将被分配到老年代中。(注意在JDK8中已经没有老年代的概念,使用的是metaspace的概念,感兴趣请参考 jdk8 Metaspace 调优)

垃圾回收算法详解(引用计数/标记-清除/标记压缩/复制算法)_第8张图片

为什么需要年轻代:

按照GC的运行机制,会回收掉已经死掉的对象,而对象一般都是在年轻代就会死去,所以年轻代比老年代需要更频繁的GC清理,下面针对年轻代与老年代的回收机制有不同的讲解

存在的问题:

空间浪费,需要整合标记。所以大对象一般不放在复制空间,直接进入老年代。

 

5. 几种算法对比

复制算法与标记-压缩法对比:

复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以复制算法仅仅应用在新生代,而老年代一般不能直接选用这种算法,使用标记-压缩法。

标记-压缩法与标记-清除法对比:

根据老年代的特点,有人提出了“标记-压缩”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。也可以减少内存碎片。

---------------------------------------------------------------------------------------------------------------------------------------------------

感谢您的观看!

参考资料和图片来源:

https://blog.csdn.net/qq_33048603/article/details/52727991

https://www.cnblogs.com/ityouknow/p/5614961.html

你可能感兴趣的:(面试经验,基础知识)