老年代的垃圾回收算法

3,255 阅读6分钟

之前看过了垃圾回收算法的新生代GC,也是使用的一种比较浪费内存的复制算法,晚上看书又接着往下看了一点,

堆 = 新生代+老年代,但是要注意一点老年代不包括永久代(方法区),也就是说堆内存中只有新生代和老年代,而永久代是指的方法区。

之前介绍过新生代中的垃圾回收机制了,再来介绍一下老年代的垃圾回收机制里面使用到的算法。


新生代GC:MinorGC 之前介绍过了不说了,复制算法图解也比较清晰

老年代GC:FullGC 我们先说FullGC出现的原因吧,FullGC是老年代的GC,在新生代如果说存在的对象或者说新创建 出来的对象由于某些原因需要移动到老年代中,但是老年代中压根就没有这么大的内存空间去容纳这个对象, 那么就会引发一次FullGC,如果在执行完FullGC之后,还是没有办法给这些对象分配内存,那么凉了,该抛出异常了,异常类型就是OutOfMemoryError。


而FullGC使用的是和MinorGC不一样的算法,它使用的是标记清除算法,听名字,挺好理解的,来波图示解析一波。 深入了解JVM一书中的图示是这个样子的,

看名字的话是先标记,然后在删除。这也是也给最最基本的算法。 这个算法就是分两个步骤

•标记(Mark)过程:找到所有的可以访问的对象,做个指定的标记。

•清除(Swep)过程:遍历堆内存,把未标记的对象进行一个回收。


之前看一些文档上说,先标记,然后把没有标记的对象给回收掉,其实意思都差不多,但是在深入理解JVM一书中说到,首先标记出所有的需要回收的对象,在标记完成之后统一回收所有的被标记的对象, 其实我的理解和书中感觉有点不太一样,不过区别也不大。我说说我的理解吧。



在了解了这个之后,我们还得说一个概念,那就是GC Root,Root我们可以理解成一个根节点就像这个样子


上图中的a,b,c,d,就是活着的对象,如果说存在这引用,比如说b引用的a,那么a他就是属于活着的对象。 当我们老年代内存区中的有效的内存空间不够的时候,那么这时候整个世界都要安静下来了(stop the world),这时候就要开始准备进行垃圾回收了。


•标记:遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。就是我们图中所标记的a,b,c,d.•清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。 也就是说,如果内存不够,GC线程就会被触发然后将程序暂停,随后将依旧存活的对象标记一遍,最后再将堆中所有没被标记的对象全部清除掉,接下来便让程序继续恢复运行。


流程图就想这个样子的 初始下的老年代中的对象状态



这时候都是没有被标记的状态,接下来内存不够,GC线程停止,开始进行标记了



按照根节点开始遍历 标记的abcdeh都是存活的对象,接下来开始标记。



接下来就是清除数据了,这个就更加的简单了



清楚完成之后还有就是把标记去除掉,可以下次进行标记清除的时候继续清除


这样标记清除就执行完毕了,剩下还有两个要说的地方,

一是在进行标记清楚算法的时候为什么要让程序停止,(stop the world)。

二是标记清除算法的优点和缺点又是什么?(Stop the World)


程序停止其实可以理解,因为如果说不停止程序的话,我们在标记完成这个b对象之后,我们new出一个新的对象J,可以从B指向J,也就是说,这时候J应该是被标记的状态,但是实际情况肯定不是,这个对象在B标记完之后,马上都要结束了,我们又new出来一个对象,可想而知,他肯定是没有被标记的,所以在第二阶段进行清除的时候,这个苦命的J将会被清除掉, 那这样肯定是不符合我们的实际情况的。


你想呀这刚刚new出来的对象结果被清除了,忽然变成了空值,那就不符合我们的要求了。所以他会让程序先停止,然后不会再出现这种情况,然后进行开始标记阶段。


优缺点

首先我们可以先看缺点,他的缺点非常明显,

•因为他会递归遍历Root,这样的话 Stop the World的时间就比较长了,这样一直让人等待的滋味可不是那么好受。•第二个就是这种清除方式清除出来的内存空间是不连续的,你看这个图


死亡的上下分成了2部分,是不连续的,这样给JVM又造成了一种额外的负担,他需要去维持一个内存的空闲列表,如果说我们在这时候去new一个数组,你想想他去找这个连续的内存空间的话,是不是就要困难很多呢?


他的优点也有,

•比如说不会出现循环引用, 我们可以想想 两个类 互相引用,A中newB,B中newA,那这样岂不是a.b=b ,b.a = a ,是吧 ,而标记清除算法在走完了之后,是可以回收a,和b的,因为他是从根元素开始遍历标记,也就是从ab开始,那么单一的a和单一的b就是没有被标记的,所以,这样就避免了循环引用的问题•还有一点感觉没啥区别,都是内存不够的时候才进行的引用。这没啥说的。


标记--整理算法


而因为标记--清除算法会导致内存分配都出现了各种不均匀的空间,这时候就有了另外的一种算法,直接把那些存活的对象标记出来,然后给他怼到内存空间边界,然后剩下的直接全给他清除了。这方法图解看的一清二楚,剩下的都是和标记清除算法一样的,好像没啥解释的,直接上图

书中你看就是把存活的都给怼到内存空间的上边,你也可以随便的理解成上下左右都ok。

以上就是堆内存中的老年代的两种垃圾回收算法了,如果有不合适的,希望大佬可以指正,一起讨论一下。


Java 极客技术公众号,是由一群热爱 Java 开发的技术人组建成立,专注分享原创、高质量的 Java 文章。如果您觉得我们的文章还不错,请帮忙赞赏、在看、转发支持,鼓励我们分享出更好的文章。

关注公众号,大家可以在公众号后台回复“掘金”,获得作者 Java 知识体系/面试必看资料。