美文网首页
BiBi - JVM -3- 垃圾收集算法

BiBi - JVM -3- 垃圾收集算法

作者: 奋飞的蜗牛ing | 来源:发表于2019-02-27 23:23 被阅读0次

    From:深入理解Java虚拟机

    程序计数器、虚拟机栈、本地方法栈这3部分区域随线程而生而灭,每一个栈桢中分配多少内存基本是在编译期间就可知的,他们的分配和回收具有确定性,不需要考虑回收的问题。而Java堆和方法区不一样,一个方法中的多个分支需要的内存是不一样的,需要程序处于运行期间才能知道要创建哪些对象,这部分内存的分配和回收是动态的。

    • GC需要完成的3个件事情:
      1)哪些内存需要回收?
      2)什么时候回收?
      3)如何回收?

    1. 判断对象“存活”的方法

    • 引用计数算法【不好】

    给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是没有被引用,死亡的对象。
    问题:难以解决对象之间互相循环引用
    例子:对象a、b已经不能再被访问了,但是他们因为互相引用这对方,导致他们的引用计数都不为0。所以,主流的Java虚拟机没有采用这种方法进行内存管理。

      public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.obj = b;
        b.obj = a;
        a = null;
        b = null;
        System.gc();
      }
    
    • 可达性分析算法【主流程序语言采用的方法】

    通过一系列【GC Roots】对象作为起始点,从这些节点向下搜索,所走过的路径称为【引用链】,当一个对象到GC Roots没有任何引用链相连时,则此对象不可用。

    • Java中可以作为GC Roots的对象包括:
      1)虚拟机栈中引用的对象
      2)本地方法栈中JNI引用的对象
      3)方法区中类静态属性引用的对象
      4)方法区中常量引用的对象

    2. finalize()方法

    宣告一个对象真正死亡,至少要经历两次标记过程:
    第一次:对象进行可达性分析后发现没有与GC Root相连的引用链,进行第一次标记,并且还要进行一次筛选,筛选得条件是该对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过了,虚拟机将这两种情况视为【没有必要执行】
    第二次:对象有必要执行finalize()方法,将这个对象放置在F-Queue队列中,并由一个低优先级的Finalizer线程去执行它。finalize()方法是对象逃离死亡命运的最后机会,GC会对F-Queue队列中的对象进行第二次小规模的标记

    注意:任何一个对象的finalize()方法只会被系统自动调用一次,所以在finalize()方法中逃脱的对象,只能够逃脱一次。
    F-Queue队列的执行,无法保证各个对象的调用顺序。所以,最好不要在finalize()中做任何事情。

    3. 回收方法区

    永久代的垃圾回收主要分两部分:【废弃的常量】和【无用的类】。回收废弃的常量跟Java堆中对象的回收类似。

    • 无用类的判断条件:
      1)Java堆中不存在该类的任何实例
      2)加载该类的ClassLoader已经被回收
      3)该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类

    在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景,需要虚拟机具备【类卸载的功能】,以避免永久代溢出。

    4. 垃圾收集算法

    • 1)标记 - 清除算法

    分两个阶段,先标记出需要回收的对象,在标记完成后再统一回收所有标记的对象。
    缺陷:标记和清除两个过程效率不高;标记清除后会产生大量的不连续的内存碎片,当程序运行过程中需要分配大对象时,由于无法找到连续的内存空间,而不得不提前触发另一次垃圾回收动作。

    • 2)复制算法【JVM新生代使用的算法】

    将内存按容量分为大小相等的两块,每次只使用其中一块。当这块内存用完,就将还存活的对象复制到另外一块上面,然后再把已使用过的这块内存空间一次全部清理掉
    优点:实现简单、效率高、没有锁片。
    缺陷:内存空闲一半,浪费。

    因为【新生代】中的对象98%都是“朝生夕死”【存活率不高】,适合用复制算法来回收,但并不一定按照1:1来划分内存空间。

    HotSpot划分比例为Eden:Survivor1:Survivor2 = 8:1:1,每次使用Eden和其中一个Survivor,当回收时,将Eden和Survivor1中存活着的对象一次性复制到Survivor2中,最后清理掉Eden和Survivor1空间。这样新生代只浪费了【10%】的内存空间。
    当新生代中回收的空间大于10%,即Survivor2空间不够用时,多的对象将直接通过【分配担保机制】进入到【老年代】。

    • 3)标记 - 整理【JVM老年代使用的算法】

    与标记-清除算法一样,只是不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存

    老年代中对象【存活率高】【没有额外的空间进行担保】,所以必须使用:标记-清理/整理算法,不能使用复制算法进行担保。

    相关文章

      网友评论

          本文标题:BiBi - JVM -3- 垃圾收集算法

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