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

  1. finalize():方法是一个在Object类中定义的方法,如果我们重写了finalize()方法,那么在对象被回收之前将会调用finalize()方法,如果我们在finalize()方法中将对象和某个还在生命周期的对象关联上,那么这个对象还有可能在回收之前被复活,当然这种机会只有一次,当第二次遇到回收时,将不会再调用finalize方法。
  2. Java对象是否存活的判断算法——根搜索算法
  • 这个算法的思路其实很简单,它把内存中的每一个对象都看作一个节点,并且定义了一些对象作为根节点“GC Roots”(GC 根)。如果一个对象中有另一个对象的引用,那么就认为第一个对象有一条指向第二个对象的边,如下图所示。JVM会起一个线程从所有的GC Roots开始往下遍历,当遍历完之后如果发现有一些对象不可到达,那么就认为这些对象已经没有用了,需要被回收。
    在这里插入图片描述
    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()方法中的内容。
  • 软引用输出结果
    在这里插入图片描述
  • 弱引用比软引用更加脆弱,弱引用的对象将会在下一次的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进行垃圾回收,在内存还是充足的情况下依然回收了弱引用的对象,执行结果如下:
    在这里插入图片描述
  • 虚引用是最脆弱的引用,我们无法通过一个虚引用来获得对象,即使在没有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之后就可以加入到引用队列。运行结果如下:
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值