java Reference object

概念

从概念上说
SoftReference软引用,当内存不足的时候gc时才会清理
WeakReferences弱引用,当触发gc就会被清理
PhantomReferences幻象引用,就是不存在的对象,get就会返回null,一般和reference queue结合使用

作用

SoftReference

SoftReference一般会用来保存内存敏感对象(比如缓存文件或者图片)。
下面是一个读文件code:

    public static void main(String[] args) throws Exception {
        String path = "E:\\download\\CentOS-7-x86_64-DVD-1708.iso";
        FileInputStream fileInputStream = new FileInputStream(path);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
//        StringBuilder sb = new StringBuilder();
//        String temp;
//        while ((temp = bufferedReader.readLine()) != null) {
//            sb.append(temp).append('\n');
//        }
//        System.out.println(sb.toString());

        SoftReference<List<String>> reference = new SoftReference<>(new LinkedList<>());
        String temp;
        while ((temp = bufferedReader.readLine()) != null) {
            List<String> tempList = reference.get();
            if (tempList != null) {
                tempList.add(temp);
            } else {
                throw new Exception("文件太大");
            }
            //tempList = null;
        }
        List<String> tempList = reference.get();
        if (tempList != null) {
            tempList.forEach(System.out::println);
        } else {
            System.out.println("null");
        }
    }

就这样一段代码,2种读文件方式都会产生OutOfMemoryError。但是第二种使用SoftReference,异常是可控的。调用get()方法时,返回null,表示产生OutOfMemoryError之前,触发gc回收SoftReference保存的对象。(注意,在这个地方使用LinkedList来保存数据是因为每次add增加内存变化很小;如果使用StringBuilder的append方法,每次都会new String(),这样每次都需要额外保存很大内容,很容易在append方法处产生outOfMemoryError,此时,reference.get()的StringBuilder对象被持有,属于强引用,gc时就不会回收了)

WeakReferences

个人来说没太理解,有看说WeakReferences可以来关联没直接关系对象和消除map中重复数据。按这个观点去找的资料看的代码。
ObjectStreamClass中的WeakClassKey来将一个class转成一个identityHashCode,ObjectInputStream和ObjectOutputStream调用ObjectStreamClass的lookup方法,获取在Caches的localDescs中的ObjectStreamClass直接返回。
ObjectStreamClass的lookup方法

static ObjectStreamClass lookup(Class<?> cl, boolean all) {
        if (!(all || Serializable.class.isAssignableFrom(cl))) {
            return null;
        }
        // ReferenceQueue中存在表示已清除,清除map中的过时key
        processQueue(Caches.localDescsQueue, Caches.localDescs);
        WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
        Reference<?> ref = Caches.localDescs.get(key);
		...
		EntryFuture future = null;
        if (entry == null) {
        	// 这里使用SoftReference,是考虑map中的key因为gc被清理了,value还存在,如果出现内存不足时,SoftReference对象也会被清理,防止内存泄漏??
        	// 这里代码逻辑就是,循环设置一直到内存足够entry != null,最后再次判断if (entry == null)是考虑刚缓存添加成功了,又被清理了??
            EntryFuture newEntry = new EntryFuture();
            Reference<?> newRef = new SoftReference<>(newEntry);
            do {
                if (ref != null) {
                    Caches.localDescs.remove(key, ref);
                }
                ref = Caches.localDescs.putIfAbsent(key, newRef);
                if (ref != null) {
                    entry = ref.get();
                }
            } while (ref != null && entry == null);
            if (entry == null) {
                future = newEntry;
            }
        }
        if (entry instanceof ObjectStreamClass) {  // check common case first
            return (ObjectStreamClass) entry;
        }
        ...
    }

ThreadLocal、Thread关联
Thread中持有ThreadLocal.ThreadLocalMap对象threadLocals,ThreadLocal.ThreadLocalMap中的Entry继承自WeakReference,Map的key使用ThreadLocal。一个Thread中map保存多个ThreadLocal对应的entry,当调用get方法时,使用强引用持有entry的value,当没有强引用持有entry当发生gc时就会清理entry,再次调用就会清理key中entry为null的值,并且使用setInitialValue拿到初始值。
使用WeakReference不会因为ThreadLocal被清理,每个未结束的线程中的ThreadLocalMap发生内存泄漏。

PhantomReferences

PhantomReferences和reference queue结合使用,可以用来跟踪对象回收,当ReferenceQueue调用poll不为null时,表示发生了gc,这个时候可以用来记录日志或者做一些非堆内存的清理操作。

    static class Test {
        private int num;
        private String str;
        private PhantomReference reference;

        public void setReference(PhantomReference reference) {
            this.reference = reference;
        }

        public PhantomReference getReference() {
            return reference;
        }
    }

    public static void main(String[] args) throws InterruptedException {
     	// 使用对象持有phantomReference对象
        ReferenceQueue referenceQueue = new ReferenceQueue<>();
        Test abc = new Test();
        new Thread(() -> {
            Test t = new Test();
            abc.setReference(new PhantomReference<>(t, referenceQueue));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(1500);
        System.gc();

        Thread.sleep(1000);

        Reference reference = referenceQueue.poll();
        if (reference != null) {
            System.out.println(reference.toString());
        }
    }

需要注意,PhantomReference对象本身需要持有,不然会被gc时也会被清理。
java8里面有个sun.misc.Cleaner对象,java.nio.DirectByteBuffer实例化时通过Cleaner.create(this, new Deallocator(base, size, cap))实例化一个cleaner对象,当this自身被清理时,调用clean方法,运行runnable来清理unsafe.allocateMemory分配的gc时无法处理的内存。

参考文档

  1. http://www.kdgregory.com/index.php?page=java.refobj
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值