WeakReference, SoftReference, ReferenceQueue学习与实验

WeakReference, SoftReference

Java中有四种引用类型,分别是Strong, Soft, Weak, Phantom

Strong ref:强引用,被引用的对象在gc的时候不会被回收。

我们先来理解一下这句话是什么意思。先上一小段代码

int num = 100;
Object ref = new Object();

上面两行代码,第一行声明了一个整型变量,它是放在程序栈上的。

它是放在程序栈上的。

这又是啥意思呢?我们给上面的代码包在方法里面。

public void static main(String[] args) {
    foo();
}

public void foo() {
    int num = 100;
    Object ref = new Object();
}

放在程序栈上的意思,就是int num这个变量,在foo方法执行完毕后,就因为程序栈退栈而被销毁了,所以使用到的内存也被回收了。

但是Object ref的情况就有些不同了。ref这个引用本身是放在程序栈上的,它也随着foo方法执行完毕,退栈而销毁,但是后面new Object所创建的数据,是存放在heap上的,当foo方法执行完毕后,Object对象还不一定被回收了。

在上面代码片段中,Object这个存在于heap上的数据块,就被ref这个引用(名词)所引用(动词)着。

上面的句子读着难受,我们换个说法:Object这个存在于heap上的数据块,就被ref这个指针所引用着。

如果把它们想象成现实世界的实物,那么我手里拿着ref这个对象,我就能发现它上面连着一根线,顺藤摸瓜,我们就能找到一个叫Object的数据。

再回到四种引用上面来:

  • Strong ref:强引用,被引用的对象在gc的时候不会被回收。
  • Soft ref:软引用,它引用的东西会在触发OOM的时候释放掉,换句话说,soft引用的东西和普通的没区别,但是当内存紧张的时候,可能会被释放掉。
  • Weak ref: 弱引用,当一个对象只有弱引用引用时,会在gc的时候被回收掉。
  • Phantom ref:虚引用,emm,总之就是有点玄学,还不知道具体啥用。

ReferenceQueue

这个队列的作用,是当GC将一些引用回收的时候,将*Reference对象放到这个队列里面,这样就可以通过poll方法获取到了。

也就是说,这个对象一开始是空的,每当系统GC回收掉WeakReference或者另外两种引用所指向的对象,就会将这个Reference对象放进这个Queue中。相当于告诉你:“我已经回收掉一些对象了,你看看需不需要做什么特殊的操作”。

光看文档和各种文章,其实还是一脸懵逼,于是我做了下面一个实现。

实验

注意以下:

  • weak1是直接引用一个对象,这个对象没有被任何强引用持有,因此按预期,一次GC后,就会被回收
  • weak2是被一个强引用持有,同时也被一个weak引用持有,按照预期,强引用一直在的话,是不会被回收的。如果它与强引用断开关系,那么会被回收。
  • list里面是存放Soft引用,我会一直往里面加数据,按照预期,当触发GC的时候,会回收里面的数据。
  • ReferenceQueue,我使用另一个独立的线程一直去poll里面的值,当有数据被回收的时候,就可以poll到一个东西。
public class Main {

    public static class Data {
        int mVar = 0;

        byte[] bitmap;

        public Data(int d) {
            mVar = d;
        }

        public Data(byte[] data) {
            bitmap = data;
        }
    }

    public static void main(String [] args) {

        ReferenceQueue<WeakReference> queue = new ReferenceQueue<>();

        WeakReference<Data> weak1 = new WeakReference(new Data(100), queue);

        Data fuck2 = new Data(200);
        WeakReference<Data> weak2 = new WeakReference(fuck2, queue);

        int i = 1;

        List<SoftReference<Data>> list = new LinkedList<>();

        // 这里在实验ReferenceQueue的作用
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (queue.poll() != null) {
                        System.out.println("--------------- t2: something is null");
                    }
                }
            }
        }).start();

        while (true) {

            if (i == 10) {
                fuck2 = null;
                System.out.println("let fuck2 -> null");
            }

            // 每5次,就执行一下gc
            if (i % 5 == 0) {
                Runtime.getRuntime().gc();
                System.out.println("try gc...");
            }

            // 这里是在尝试占用内存,触发soft回收
            if (i >= 15) {
                System.out.println(Runtime.getRuntime().freeMemory() + ", " + Runtime.getRuntime().maxMemory() + ", " + Runtime.getRuntime().totalMemory());
                list.add(new SoftReference(new Data(new byte[(int) (Runtime.getRuntime().freeMemory())]), queue));
                Runtime.getRuntime().gc();
            }

            // fuck1数据只有弱引用引用,所以第一次gc就会回收
            Data data = weak1.get();
            if (data == null) {
                System.out.println("fuck1 hasBeen release");
            } else {
                System.out.println("fuck1 living!");
            }

            // fuck2 有一个强引用,有一个弱引用,当第10次执行的时候,移除强引用,再gc,就会被回收
            Data dat2 = weak2.get();
            if (dat2 == null) {
                System.out.println("fuck2 hasBeen release");
            } else {
                System.out.println("fuck2 living!");
            }

            i++;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

接下来看输出的内容,我会将关键的信息用注释放在输出内容的后面

fuck1 living!
fuck2 living!
fuck1 living!
fuck2 living!
fuck1 living!
fuck2 living!
fuck1 living!
fuck2 living!
try gc... // 首次GC
fuck1 hasBeen release // weak1 被释放,符合预期
fuck2 living! // weak2 没有被释放,符合预期
--------------- t2: something is null // 新线程poll到东西了,说明刚刚有东西被释放,符合预期
fuck1 hasBeen release
fuck2 living!
fuck1 hasBeen release
fuck2 living!
fuck1 hasBeen release
fuck2 living!
fuck1 hasBeen release
fuck2 living!
let fuck2 -> null // 手动接触weak2的强引用关系
try gc...
fuck1 hasBeen release
--------------- t2: something is null // queue又poll到了,符合预期
fuck2 hasBeen release // weak2也被回收了,符合预期
fuck1 hasBeen release
fuck2 hasBeen release
fuck1 hasBeen release
fuck2 hasBeen release
fuck1 hasBeen release
fuck2 hasBeen release
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
255742864, 3817865216, 257425408 // 接下来开始一直给list加数据
fuck1 hasBeen release
fuck2 hasBeen release
255851920, 3817865216, 513277952
fuck1 hasBeen release
fuck2 hasBeen release
255852440, 3817865216, 769130496
fuck1 hasBeen release
fuck2 hasBeen release
255852440, 3817865216, 1024983040
fuck1 hasBeen release
fuck2 hasBeen release
255852440, 3817865216, 1280835584
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
255852536, 3817865216, 1536688128
fuck1 hasBeen release
fuck2 hasBeen release
256376632, 3817865216, 1793064960
fuck1 hasBeen release
fuck2 hasBeen release
256376728, 3817865216, 2049441792
fuck1 hasBeen release
fuck2 hasBeen release
256376728, 3817865216, 2305818624
fuck1 hasBeen release
fuck2 hasBeen release
256376728, 3817865216, 2562195456
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
256376752, 3817865216, 2818572288
fuck1 hasBeen release
fuck2 hasBeen release
123207480, 3817865216, 2941779968
--------------- t2: something is null   // 注意到这里,终于触发了GC,一下子poll了很多数据,说明soft里面的引用数据都被回收了
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
fuck1 hasBeen release
fuck2 hasBeen release
160092384, 3817865216, 286261248
fuck1 hasBeen release
fuck2 hasBeen release
244181792, 3817865216, 532152320
fuck1 hasBeen release
fuck2 hasBeen release
244318104, 3817865216, 776470528
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
244318200, 3817865216, 1020788736
fuck1 hasBeen release
fuck2 hasBeen release
244842296, 3817865216, 1265631232
fuck1 hasBeen release
fuck2 hasBeen release
244842392, 3817865216, 1510473728
fuck1 hasBeen release
fuck2 hasBeen release
244842392, 3817865216, 1755316224
fuck1 hasBeen release
fuck2 hasBeen release
244842392, 3817865216, 2000158720
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
244842488, 3817865216, 2245001216
fuck1 hasBeen release
fuck2 hasBeen release
245366584, 3817865216, 2490368000
fuck1 hasBeen release
fuck2 hasBeen release
245366680, 3817865216, 2735734784
fuck1 hasBeen release
fuck2 hasBeen release
241025560, 3817865216, 2981101568
fuck1 hasBeen release
fuck2 hasBeen release
234681496, 3817865216, 3213885440
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
fuck1 hasBeen release
fuck2 hasBeen release

总结

这样,基本就明白了WeakReference和SoftReference,以及ReferenceQueue的特性了。

WeakReference可以使用在一些observer list上,比如你的界面需要持续监听一个事情,需要严格的调用成对的方法,addListener,removeListener,但是保不齐什么时候忘记了,或者因为一些特殊的情况,导致没有调用remove,这样你这个界面整个对象都被listener list一直持有了,就相当于内存泄露了。这个时候,可以使用WeakReference,这样就算没有remove,gc也能干掉。

SoftReference可能在实现一些缓存上会比较有用。

ReferenceQueue相当于给我们一个能力去知道我的对象什么时候被回收了,那么如果需要做一些后续的事情,就可以依靠这个时机。

参考资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值