欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM如何判断对象是否可以被回收

程序员文章站 2022-04-07 09:04:24
...
  1. finalize():方法是一个在Object类中定义的方法,如果我们重写了finalize()方法,那么在对象被回收之前将会调用finalize()方法,如果我们在finalize()方法中将对象和某个还在生命周期的对象关联上,那么这个对象还有可能在回收之前被复活,当然这种机会只有一次,当第二次遇到回收时,将不会再调用finalize方法。
  2. Java对象是否存活的判断算法——根搜索算法
  • 这个算法的思路其实很简单,它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roots”(GC 根)。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,如下图所示。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。
    JVM如何判断对象是否可以被回收
    3. 这个算法的关键就在于GC Roots的定义,有四种可以作为GC Roots的对象
  • 第一种是虚拟机栈中的引用的对象,我们在程序中正常创建一个对象,对象会在堆上开辟一块空间,同时会将这块空间的地址作为引用保存到虚拟机栈中,如果对象生命周期结束了,那么引用就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引用,就说明这个对象还是有用的,这种情况是最常见的。
  • 第二种是我们在类中定义了全局的静态的对象,也就是使用了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引用会保存在共有的方法区中,显然将方法区中的静态引用作为GC Roots是必须的。
  • 第三种便是常量引用,就是使用了static final关键字,由于这种引用初始化之后不被修改,所以方法区常量池里的引用的对象也会作为GC Roots。
  • 最后一种是在使用JNI技术时,有时候单纯的Java代码并不能满足我们的需求,我们可能需要在Java中调用C或C++的代码,因此会使用native方法,JVM内存中专门有一块本地方法栈,用来保存这些对象的引用,所以本地方法栈中引用的对象也会被作为GC Roots。
  1. Java中的引用一共有四种,它们分别是强引用、软引用、弱引用和虚引用
  • 强引用就是我们平常用的类似于“Object obj = new Object()”的引用,只要obj的生命周期没结束,或者没有显示地把obj指向为null,那么JVM就永远不会回收这种对象。
  • 软引用相对强引用来说就要脆弱一点,JVM正常运行时,软引用和强引用没什么区别,但是当内存不够用时,濒临逸出的情况下,JVM的垃圾收集器就会把软引用的对象回收。在JDK中提供了SoftReference类来实现软引用,如下图的代码示例所示:
public class TestSoftReference {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("当内存溢出时,调用垃圾回收机制");
    }
    public static void main(String[] args) {
        TestSoftReference test = new TestSoftReference();
        SoftReference<TestSoftReference> reference = new SoftReference<>(test);
        test = null;
        // get():返回此引用对象的指示对象。 如果此引用对象已经由程序或由垃圾收集器,则此方法返回null
        System.out.println(reference.get());
        List list = new ArrayList<String>();
        int i = 0;
        while (i++ < 1000000){
            list.add(String.valueOf(i));
        }
        System.out.println("结束");
    }
}
  • 如上代码,我们重写finalize()方法,然后再main()方法中往List集合中不断加值,我们的while循环次数只要相对的多一点,就会出现内存溢出,然后会去调用finalize()方法,输出finalize()方法中的内容。
  • 软引用输出结果
    JVM如何判断对象是否可以被回收
  • 弱引用比软引用更加脆弱,弱引用的对象将会在下一次的GC时被回收,不管JVM内存被占用多少。在JDK中使用WeakReference来实现弱引用,代码示例如下:
public class TestWeakReference {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("弱引用,不管任何时候调用垃圾回收机制,都会被回收");
    }

    public static void main(String[] args) throws InterruptedException {
        TestWeakReference test = new TestWeakReference();
        ReferenceQueue<TestWeakReference> queue = new ReferenceQueue<>();
        WeakReference<TestWeakReference> reference = new WeakReference<>(test, queue);
        test = null;
        System.out.println(reference.get());
        System.gc();
        Thread.sleep(100);
        System.out.println(reference.get());
    }

}
  • 在以上代码中我们主动的调用了一次GC进行垃圾回收,在内存还是充足的情况下依然回收了弱引用的对象,执行结果如下:
    JVM如何判断对象是否可以被回收
  • 虚引用是最脆弱的引用,我们无法通过一个虚引用来获得对象,即使在没有GC之前。虚引用需要和一个引用队列配合使用。
public class TestPhantomReference {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("虚引用,垃圾回收机制");
    }
    public static void main(String[] args) throws InterruptedException {
        TestPhantomReference test = new TestPhantomReference();
        ReferenceQueue<TestPhantomReference> queue = new ReferenceQueue<>();
        PhantomReference<TestPhantomReference> reference = new PhantomReference<>(test, queue);
        test = null;

        System.out.println(reference.get());
        // isEnqueued():告诉该引用对象是否已由程序或垃圾收集器添加到队列。如果此引用对象在创建时未在队列中注册,则此方法将始终返回false
        System.out.println(reference.isEnqueued());
        System.gc();
        Thread.sleep(100);
        System.out.println("---------------------");
        System.out.println(reference.get());
        System.out.println(reference.isEnqueued());
        // remove():移除此队列中的下一个引用对象,阻塞直到一个可用对象或给定的超时时间到期为止。
        // 一个参考对象(如果在指定的超时时间内可用),否则为 null
        System.out.println(queue.remove(2000));
        System.gc();
        Thread.sleep(100);
        System.out.println("---------------------");
        System.out.println(reference.get());
        System.out.println(reference.isEnqueued());
        System.out.println(queue.remove(2000));
    }
}
  • 于虚引用没有办法访问对象实例,所以我们无法通过对象实例来判断是否被回收,但是我们传入引用队列,在对象被真正清除时,将会被加入到引用队列中,referenceQueue.remove(2000)将会阻塞2秒等待对象入队列,并移除打印。可以看下图输出,可以看出第一次gc虽然执行了finalize方法,但是对象并没有马上被清除,而是在第二次gc的时候才真正被清除。这是由于PhantomReference的处理过程和上面的引用不同,如果重写了finalize方法,那么必须保证finalize方法运行完之后才能加入引用队列,所以如果将代码中的finalize方法去掉,那么在第一次gc之后就可以加入到引用队列。运行结果如下:
    JVM如何判断对象是否可以被回收
相关标签: JAVA经典面试题