美文网首页
垃圾收集算法

垃圾收集算法

作者: 史啸天 | 来源:发表于2019-11-22 16:01 被阅读0次

    概述

        上一篇我们讲到Java虚拟机的内存划分和产生内存溢出的问题;这篇就来介绍一下虚拟机中常见的判断对象存活算法和垃圾收集算法。
        说到垃圾收集(Garbage Collection GC),大部分人都把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,程序员们就在思考GC需要完成的3件:
        1、那些内存需要回收?
        2、什么时候回收?
        3、如何回收?

    对象已死吗

        针对对象回收,首先我们要解决的问题就是,如何判断一个对象是否还有存活的必要,引用计数法和可达性分析算法是虚拟机常用的判断对象是否可以回收的算法。

    引用计数算法

        引用计数算法(Reference Counting)就是给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
        客观地说引用计数算法的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法;但遇到对象之间循环引用的时候,就很难用这种算法也判定对象是否可以被回收,如下图所示:


    对象A持有对象B,对象B持有对象A.png

        那么此时我们就需要采用另外一种可达性分析算法。

    可达性分析算法

        可达性分析(Reachability Analysis)算法基本思路就是通过一系列的成为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。如下图所示:


    可达性分析算法判定对象是否可回收.jpg

    再谈引用

        说到引用,在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种:
        (1)、强引用,就是指在程序代码中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
        (2)、软引用,是用来描述一些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中,进行二次回收。
        (3)、弱引用,也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会被回收掉。
        (4)、虚引用,也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

    生存还是死亡

        在虚拟机中,如果对象进行可达性分析后发现没有与GC Roots相连接的引用链,这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。

    垃圾收集算法

    标记-清除算法

        标记-清除(Mark-Sweep)算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,之后统一回收所有标记对象。如下图所示:


    标记-清除算法示意图.jpg

        这种算法虽然简单但是有很大的不足:一个是效率问题,标记和清除两个过程效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后程序运行过程中需要分配较大对象时,无法找到足够的连续内存。

    复制算法

        复制(Copying)的收集算法出现了,它将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。如下图所示:


    复制算法示意图.jpg

        这样的方式,每次都只对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。这种算法的代价是将内存缩小为了原来的一半,代价未免太过高昂。
        但并非如此,IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按1:1的比例来划分内存空间,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。
        虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%,只有10%的内存会被“浪费”。

    标记-整理算法

        复制收集算法在对存活率较高的对象时,效率就会变低。因此有人提出了“标记-整理”(Mark-Compact)算法。
        标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。如下图所示:


    标记-整理算法示意图.jpg

    分代收集算法

        分代收集(Generational Collection)算法,是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
        新生代中每次垃圾收集时都发现有大批对象死去,只有少量对象存活,因此采用复制算法;而老年代中因为存活率高,就必须使用“标记-清除”或者“标记-整理”算法来进行回收。

    总结

        这篇为大家介绍了判断对象存活算法:引用计数法和可达性分析算法;垃圾收集算法:标记-清除算法、复制算法和标记整理算法;希望大家能够更加深入的了解Java虚拟机,在今后的面试中,对答如流。

    相关文章

      网友评论

          本文标题:垃圾收集算法

          本文链接:https://www.haomeiwen.com/subject/ejfqwctx.html