JVM中垃圾回收第一步——哪些对象需要被回收

169 阅读5分钟

这是我参与更文挑战的第12天,活动详情查看:更文挑战

  一个对象是否要回收,最直接的考虑就是这个对象是否还能用到。如果对象不能用到,就说这个对象“已死”,那对象占用的内存就可以回收。在代码中,对对象的使用通常是通过一个指向对象的引用来实现的,因此在判定对象是否“已死”,可以通过对象的引用入手。

1 引用计数算法

  引用计数算法:在对象中添加一个引用计数器,每当一个地方引用对象时,则计数器值加一;当引用失效时,计数器值就减一;当对象的引用计数为0时,对象将无法再被使用到,此时对象“已死”,可以被回收。

  引用计数算法简单、高效,可以说是一个非常不错的算法,但是Java并不是使用引用计数算法来实现内存管理。原因在于,这个看似简单的算法,很多情况下需要配合很多额外的处理才能保证正确地工作;例如对象之间的互相循环引用问题。

public class Nodes{
    private String id = null;
    
    public Nodes next = null;
    
    public Nodes(String id){
        this.id = id;
    }
    
    public Nodes(String id, Nodes nodes){
        this.id = id;
        this.next = nodes;
    }
    
    public static void main(string[] args){
        Nodes n1 = new Nodes("001");
        Nodes n2 = new Nodes("002",n1);
        n1.next = n2;
        
        n1 = null;
        n2 = null;
    }
}

  在上面这个例子中,由于 n1.next = n2;n2.next = n1; ,所以n1 和 n2的引用计数不会为0。但是因为有n1 = null;n2 = null;,两个对象都将无法被使用到。所以如果只是简单的引用计数,并无法完整地完成内存回收工作。

2 可达性分析算法

  JVM中使用可达性分析算法来判定对象是否存活。可达性分析算法:通过GC Roots的根对象作为起始节点集,从GC Roots出发根据引用关系向下搜索,搜索过程走过的路径称为引用链,如果某个对象与GC Roots之间不存在引用链,这说明这个对象不可达,说明此对象不可能再被使用。

如下图,对象 obj7 和 obj8 虽然互相引用,但是并不可达,因此将会被判定为可回收的对象。

可达性分析.png

  在Java 技术体系中,固定可作为GC Root的对象包括以下几种;

  • 在虚拟机栈(栈中的本地变量表)中引用的对象,例如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等;
  • 在方法区中类静态属性引用的对象;
  • 在方法区中常量引用的对象,例如字符串常量池里面的引用;
  • 在本地方法栈中引用的对象;
  • Java虚拟机内部的引用,如一些常驻的异常对象、类加载器等;
  • 所有被同步锁(synchronized关键字)持有的对象;
  • 反映Java虚拟机内部情况的JMXBean、JMVTI中注册的回调、本地代码缓存等。

3 对象引用

  无论是引用计数算法还是可达性分析算法,判定对象的存活都离不开引用。Java的引用概念中,分4中引用类型:

  • 强引用:是最传统的引用的定义,是指在程序代码中普遍存在的引用赋值,即类似Object o = new Object()这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  • 软引用:用来描述一些还在用,但非必须的对象。只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进到回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK1.2版之后,听过了SoftReference类来实现软引用。
  • 弱引用:用来描述一些非必须对象,但是它的强度比软引用还要更弱些。被弱引用关联的对象只能生存到下一次垃圾收集为止。当垃圾收集器开始工作,无论内存是否充足,都会回收弱引用对象。在JDK1.2版之后,听过了WeakReference类来实现弱引用。
  • 虚引用:也称为”幽灵引用“、”幻影引用“,虚引用的对象无论是否回收都无法再被使用,使用虚引用只是为了能在这个对象被垃圾收集器回收时收到一个系统通知。在JDK1.2版之后,听过了PhantomReference类来实现虚引用。

4 垃圾回收过程

  Java中的垃圾回收遵循一个特定,就是对象能不回收就不回收,因此即使通过可达性分析,判定对象为不可达也不会立即回收对象。

  一个对象的回收,要记过两次标记。

  当一个对象被判定为不可达时,会被第一次标记。被第一次标记之后,JVM会进行一次普筛,筛选条件为对象是否必须执行finalize()方法。如果对象没有覆盖finalize()方法或者finalize()方法已被调用过,则判定为“没有必要执行”。

  对于必须执行finalize()方法的对象,会被放到一个F-Queue队列中,待虚拟机自动创建一个线程Finalizer去执行F-Queue队列中对象的finalize()方法。在执行完finalize()方法之后,如果对象依然不可达,则会被第二次标记。被二次标记的对象才会被最终回收。