JVM之GC Roots

  Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径 来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会 产生内存溢出异常。

-------------------------------------------------------------------

  这里还是第二章的内容。之前早早把第二章的笔记放上去,有点草率了。这里要解释一下什么叫做GC Roots,什么叫做可达。

  首先引入一个问题:怎么判断一个对象是可以被回收的?

  我们知道Java对象实例都是被Java栈中的引用所操作的,如果说不再有引用对这个对象进行操作,那么就可以认为这个对象可以被回收了。怎么实现这个目标呢?一个简单的办法就是给对象添加一个引用计数器,有一个引用将使用这个对象时就加1,而每当一个引用失效时就减1。如此,任意时刻,这个引用计数器的值为0时,这个对象就被称为垃圾,那么垃圾回收器就可以对它进行回收操作。

  但是这个引用计数法,每有一个对象就需要维护一个引用计数器,且这个计数器本身也是有开销的;另外,还有循环引用的问题,看下面的代码及注释:

public class Test {
    public static void main(String[] args) {
        new Test().testCountUseless();
        //已经离开了两个对象的作用域,计数器都减1,但两者计数器不为0,无法被回收
        //。。。其它代码
    }

    public void testCountUseless() {
        Parent parent = new Parent();//初始化,引用计数器为1
        Son son = new Son();//初始化,引用计数器为1
        parent.setSon(son);//son被引用,son计数器加到2
        son.setParent(parent);//parent被引用,parent计数器加到2
        System.out.println("test");
    }
}

  所以,JVM一般都不采用引用计数法进行对象是否可以回收的判断。它采用GC Roots或者叫Tracing Roots的做法,用一组活跃的引用作为根集合作为引子,从每一个根向下搜索,如果某个对象与这些根集合没有任何引用链相连时,那么这个对象就被当做不可用。

  JVM一般采用哪些引用作为GC Roots引用呢?(除了Java Heap里的对象,其它的如栈,方法区里的对象基本都可以作为GC Roots)

  1.   Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的Java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,.
  2.   Thread - 活着的线程
  3.   Stack Local - Java方法的local变量或参数
  4.   JNI Local - JNI方法的local变量或参数
  5.   JNI Global - 全局JNI引用
  6.   Monitor Used - 用于同步的监控对象
  7.   Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于"JVM持有"的了。

-------------------------------------------------------------------

参考

  • https://www.yourkit.com/docs/java/help/gc_roots.jsp
  • https://blog.csdn.net/weixin_41910694/article/details/90706652

原文地址:https://www.cnblogs.com/bruceChan0018/p/15063824.html