zoukankan      html  css  js  c++  java
  • JVM中判断对象是否存活的方法

    Java中几乎所有的对象实例都存放在堆中,在垃圾收集器对堆内存进行回收前,第一件事情就是要确定哪些对象还“存活”,哪些对象已经“死去”(即不可能再通过任何途径被使用)。

    引用计数算法

      首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。
      什么是引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。那为什么主流的Java虚拟机里面都没有选用这种算法呢?其中最主要的原因是它很难解决对象之间相互循环引用的问题。在下面的代码中,我们在testGC()中new两个ReferenceCountingGC对象objA和objB,然后令objA.instance=objB,objB.instance=objA,最后令objA和objB都为null。此时,这两个对象实际上已经不可能再被访问,但由于它们互相引用着对方,导致它们的引用计数器值都不为0,于是使用引用计数算法无法通知GC收集器回收它们。

      public class ReferenceCountingGC {
    
        public Object instance = null;
    
        private static final int _1MB = 1024 * 1024;
    
        /**
         * 这个成员属性的唯一意义就是占点内存,以便在能在GC日志中看清楚是否有回收过
         */
        private byte[] bigSize = new byte[2 * _1MB];
    
        public static void testGC() {
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            objA.instance = objB;
            objB.instance = objA;
    
            objA = null;
            objB = null;
    
            // 假设在这行发生GC,objA和objB是否能被回收?
            System.gc();
        }
    
        public static void main(String[] args) {
            testGC();
        }
    }

    现在,我们在main方法中调用testGC(),然后打印GC日志(在Myeclipse中打印GC日志的方法)。得到结果为:

    0.161: [GC 4761K->568K(124416K), 0.0022505 secs]
    0.163: [Full GC 568K->471K(124416K), 0.0201927 secs]

      由“4761K->568K”我们可以得知,虚拟机并没有因为这两个对象相互引用就不回收它们,这也从侧面说明了hotspot虚拟机并不是通过引用计数算法来判断对象是否存活的

    那么,在主流的商用程序语言的主流实现中,是通过什么方法来判断对象是否存活的呢?答案是下面将要介绍的可达性分析算法。

    可达性分析算法

      可达性分析算法的基本思路是:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点考试向下探索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如下图中,对象object5、object6、object7虽然互有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。

    在Java语言中,可作为GC Roots的对象包括下面几种:
    1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
    2. 方法区中类静态属性引用的对象;
    3. 方法区中常量引用的对象;
    4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

    实例分析

    参考:https://www.zhihu.com/question/21539353/answer/95667088垃圾回收机制中,引用计数法是如何维护所有对象引用的? - Gityuan的回答 - 知乎 https://www.zhihu.com/question/21539353/answer/95667088

    以一段代码为例,从内存角度解释以上两种算法。

     public class GcDemo {
    
        public static void main(String[] args) {
            //分为6个步骤
            GcObject obj1 = new GcObject(); //Step 1
            GcObject obj2 = new GcObject(); //Step 2
    
            obj1.instance = obj2; //Step 3
            obj2.instance = obj1; //Step 4
    
            obj1 = null; //Step 5
            obj2 = null; //Step 6
        }
    }
    
    class GcObject{
        public Object instance = null;
    }

    情况(一):引用计数算法
    如果采用的是引用计数算法:

    再回到前面代码GcDemo的main方法共分为6个步骤:

    • Step1GcObject实例1的引用计数加1,实例1的引用计数=1
    • Step2GcObject实例2的引用计数加1,实例2的引用计数=1
    • Step3GcObject实例2的引用计数再加1,实例2的引用计数=2
    • Step4GcObject实例1的引用计数再加1,实例1的引用计数=2

    执行到Step 4,则GcObject实例1和实例2的引用计数都等于2

    接下来继续结果图:

    • Step5:栈帧中obj1不再指向Java堆,GcObject实例1的引用计数减1,结果为1
    • Step6:栈帧中obj2不再指向Java堆,GcObject实例2的引用计数减1,结果为1

    到此,发现GcObject实例1和实例2的计数引用都不为0,那么如果采用的引用计数算法的话,那么这两个实例所占的内存将得不到释放,这便产生了内存泄露。

     

    情况(二):可达性算法
    这是目前主流的虚拟机都是采用GC Roots Tracing算法,比如SunHotspot虚拟机便是采用该算法。 该算法的核心算法是从GC Roots对象作为起始点,利用数学中图论知识,图中可达对象便是存活对象,而不可达对象则是需要回收的垃圾内存。这里涉及两个概念,一是GC Roots,一是可达性。

     

    那么可以作为GC Roots的对象(见下图):

     

    • 虚拟机栈的栈帧的局部变量表所引用的对象;
    • 本地方法栈的JNI所引用的对象;
    • 方法区的静态变量和常量所引用的对象;

     

    关于可达性的对象,便是能与GC Roots构成连通图的对象,如下图:

     

     

     从上图,reference1、reference2、reference3都是GC Roots,可以看出:

    • reference1-> 对象实例1
    • reference2-> 对象实例2
    • reference3-> 对象实例4
    • reference3-> 对象实例4 -> 对象实例6

    可以得出对象实例1246都具有GC Roots可达性,也就是存活对象,不能被GC回收的对象。
    而对于对象实例35直接虽然连通,但并没有任何一个GC Roots与之相连,这便是GC Roots不可达的对象,这就是GC需要回收的垃圾对象。

    到这里,相信大家应该能彻底明白引用计数算法和可达性算法的区别吧

    再回过头来看看最前面的实例,GcObject实例1和实例2虽然从引用计数虽然都不为0,但从可达性算法来看,都是GC Roots不可达的对象。

    总之,对于对象之间循环引用的情况,引用计数算法,则GC无法回收这两个对象,而可达性算法则可以正确回收。

     

  • 相关阅读:
    CentOS配置启动ssh与开机自启
    CentOS中怎样安装、配置、启动Nginx
    CentOS中配置Mysql表名忽略大小写以及提示:Caused by: org.quartz.impl.jdbcjobstore.LockException: Failure obtaining db row lock的解决
    CentOS中部署jar包时提示:org.quartz.SchedulerException: Couldn't get host name
    CentOS6中怎样将jdk1.7升级到1.8
    CentOS6在使用yum install 时提示镜像源路径不存在:PYCURL ERROR 22
    信息系统项目管理师-项目立项管理考点笔记
    chrome89不再支持/deep/的解决方案
    手写async await
    proxy和reflect
  • 原文地址:https://www.cnblogs.com/thiaoqueen/p/8759374.html
Copyright © 2011-2022 走看看