Java JVM 4-GC对象判断算法:引用计数法,可达性分析算法

 因为JVM的GC(垃圾回收)机制,所以使java程序员不用再受内存分配及内存回收的烦恼(其实还是会有这些烦恼,我们以后再进行探讨),而可以将注意力集中在程序的设计上,虽然我们不用再关心这些过程,但是GC的垃圾回收我们还是要了解一下的。

 对于内存的六大区域,对于线程私有的程序计数器,java虚拟机栈,本地方法栈三个区域,它们的生命周期都是随线程开始而生,线程结束而灭,并且这三个区域的内存分配和回收具有确定性,所以这部分不是GC所关心的。

 所以,GC关心的只能是剩下的java堆与方法区了,其中99%的垃圾回收发生在java堆,而剩下的发生在方法区。

1. 如何判断对象已’死’

 Java堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经”死去”。判断对象是否已”死”有如下几种算法。

1.1 引用计数法

 引用计数描述的算法为:

给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数
器为0的对象就是不能再被使用的,即对象已”死”。

 引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个不错的算法。比如Python语言就采用引用计数法进行内存管理。

 但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题。

/**
 * JVM运行参数: -XX:+PrintGC 打印GC日志
 * @author xucc
 */

public class Test {
    public Object instance = null;
    /**
     * 内存占位用,方便垃圾回收时从日志观察效果
     */
    private static int _1MB = 1024*1024;
    private byte[] bigSize = new byte[2*_1MB];

    public static void testGC() {
        // 如果按照引用计数法,这里test1,test2创建时分别被引用一次,所以count1,count2都为1
        Test test1 = new Test();
        Test test2 = new Test();
        // 又被引用一次,count1,count2都为2
        test1.instance = test2;
        test2.instance = test1;
        // 分别取消引用一次,count1,count2都为1
        test1 = null;
        test2 = null;

        // 强制JVM进行垃圾回收
        System.gc();
    }

    public static void main(String[] args) {
        testGC();
    }
}

 如果采用引用计数判断对象,那么上面这种循环应用,对象的计数器永远不会为0,对象也就永远不会被GC回收,所以实际上GC并没有采用这种判断策略,运行结果如下:

运行结果

 内存占用从9M -> 1M,证明占位用的数组确实被回收掉了。

1.2 可达性分析算法

 Java并不采用引用计数法来判断对象是否已”死”,而采用”可达性分析”来判断对象是否存活。

 同样采用此法的还有C#、Lisp-最早的一门采用动态内存分配的语言。

 此算法的核心思想为 : 通过一系列称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为”引用链”,当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。以下图为例:

可达性分析算法

 对象Object5-Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此他们会被判定为可回收对象。

 在java中,可以作为GC Roots的对象有如下几种:

  • 虚拟机栈中引用的对象(局部变量)
  • 方法去中类静态属性引用的对象(static对象实例)
  • 方法区中常量引用的对象(常量实例)
  • 本地方法引用的对象

猜你喜欢

转载自blog.csdn.net/weixin_40739833/article/details/80715626