JVM之对象的四种引用

1.Java的四种引用

我们平时创建对象直接返回的引用就是所谓的强引用。我们传统的强引用只能给对象两种状态:被引用|不被引用,但是对于下面的几个案例,我们的强引用却很难达到目的

  • 对于缓存这类存储空间,我们希望在空间允许的时候将其保留,空间不允许的时间将其进行回收
  • 如果我们程序中维护了一个Map<key,value>的引用,但是map中的某个键已经不能在程序的任何地方访问了,我们如何将其进行回收
  • 对于堆外分配的内存,我们如何通知操作系统将其进行回收?

2.强引用

强引用是我们平时最常用的引用,只要强引用还指向着某个对象,无论如何该对象都不会被回收,所以强引用是造成内存泄漏的主要原因,其他三种引用都不会造成内存泄漏,因为其他三种引用在特定情况下最终都会被gc回收。

3.软引用

java.lang.ref.SoftReference

软引用,当内存不足时会进行第一次gc,如果gc完成之后内存够用了则不会发生什么特殊的,但是如果第一次gc之后内存依旧不足,那么将会将只有弱引用指向的对象进行回收。

一句话:内存真实不足时会进行回收。

应用场景:缓存等对程序有用但是非必须的对象.(mybatis底层就使用到了软引用,有兴趣的可以去看看)

4.弱引用

java.lang.ref.WeakReference

只有弱引用指向的对象,下一次垃圾回收就会被清理。

应用场景:用于如果没有其他引用指向的对象,那么会直接进行回收。如开头提到的第二个问题。Java.util包下有个WeakHashMap类,这个类中的key值持有的就是弱引用,当我们程序中再也没有对key的其他引用时,下一次垃圾回收会直接将该key所对应的entry进行回收。

5.虚引用

java.lang.ref.PhantomReference

虚引用无法获取到它所指向的对象的强引用。任何时候调用其get()方法返回的都是null值,虚引用需要结合引用队列进行使用,当虚引用所指向的对象被回收时,该虚引用会被加入到引用队列中,相当于通知了系统,该虚引用指向的对象被回收了。

应用场景:堆外内存回收。

6.终结器引用

java.lang.ref.FinalReference

大家看到这个标题可能可能会想,不是四种引用吗,怎么多出来一种,我们的引用父类Reference除了上面三种实现类之外,还有另外一个包访问权限的实现类就是FinalReference终结器引用,该引用是虚拟机底层用来实现gc过程中调用finalize()方法的。

7.finalize方法调用后对象就被回收了吗?

大家平时可能都存在一个误区:对象被回收时,会先调用其finalize方法,如果在finalize方法中对象没有自救成功(在finalize方法没有和任何一个gc roots所对应的引用链建立联系),那么该对象就会被立刻回收。

实际上并不是立刻回收,而是会在下一次垃圾回收时将该对象进行回收。

下面有一个代码可以验证这个事情。

JVM参数:-XX:+PrintGCDetails

public class TestGC {
    byte[] bytes = new byte[1024 * 1024 * 20];
    public static void main(String[] args) {
        TestGC testGC = new TestGC();
        testGC = null;
        System.gc();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.gc();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize方法被调用");
    }
}

运行结果
在这里插入图片描述
我们发现第一次gc时,对应对象的20M空间并没有被回收,而是继续保存在我们的老年代,第二次垃圾回收时才真正进行回收。

finalize方法的底层原理实际上是:
在这里插入图片描述
最后在说一个,对象只有真正被回收时,才会被加入到其对应的软、弱、虚引用的引用队列中,如果该对象重写了finalize方法,那么至少要执行两次gc程序才能正常回收并被引用队列捕获,不然如果只进行了一次回收,那么从引用队列获取通知时将会造成程序阻塞。

下面这个代码是关于finalize方法和引用队列的调用顺序,有兴趣的同学可以看一下。

public class PhantomReferenceTest {
    static PhantomReference<User> phantomRef;
    static ReferenceQueue<User> queue;
    static class Damon extends Thread{
        @Override
        public void run() {
            while (true){
                if (queue != null){
                    try {
                        //对象真正被回收时,其对应的虚引用会被加入到引用队列中
                        Reference<? extends User> remove = queue.remove();
                        if (remove != null){
                            System.out.println("对象真正要被回收了:"+
                                    remove.getClass().getSimpleName());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Damon damon = new Damon();
        damon.setDaemon(true);
        damon.start();
//        开启守护线程
        queue = new ReferenceQueue<>();
        phantomRef = new PhantomReference<>(new User("方圆"),queue);
        System.gc();
        System.out.println("第一次GC");
        Thread.sleep(1000);

        System.gc();
        System.out.println("第二次GC");
        Thread.sleep(1000);
    }
    static class User{
        String name;

        public User(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("调用了finalize方法");
        }
    }
}

最后我们强烈不建议大家使用finalize方法,甚至需要尽量避免该方法的重写和使用,使用finally代码快会比重写finalize方法来的更加安全有效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值