【小家java】引用类型(强引用、软引用、弱引用、虚引用)

相关阅读

【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本


1、概述

本文不论述java中值传递和引用传递之间的问题(有需求的可移步理解java中值传递和引用传递),而重点讨论Java中提供了4个级别的引用:强应用、软引用、弱引用和虚引用。这四个引用定义在java.lang.ref的包下。讲述这个话题的原因,也是我第一次在集合框架里看到WeakHashMap而被带进来,闲话不多说,直接进入主题~

2、栗子

强引用( Final Reference):只要强引用还存在,垃圾收集器永远不会回收(JVM宁愿抛出OOM异常也不回收强引用所指向的对)被引用的对象。但是,强引用可能会造成可能导致内存泄露哦,这个在后续文章中会有说明

贴出JDK对强引用的源码:

class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

从类定义中可以看出,只有一个构造函数。重点是:它是非public的类哦,因此我们并不能在外部调用此构造函数来create一个强引用呢。这得益于JVM的设计,各位看官可以想想为什么呢?

软引用(Soft Reference):是用来描述一些还有用但并非必须的对象。对于软引用对象,如果内存充足gc不会管它,如果内存不够了,它就不能幸免了。在 JDK 1.2 之后,提供了 SoftReference 类可以让调用者创建一个软引用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用

先看一个最简单的使用

 public static void main(String[] args) {
   Obj obj = new Obj(); //创建一个强引用
   SoftReference<Obj> softRef = new SoftReference<>(obj);
   softRef.get(); //此方法需要注意:若softRef没被回收,返回对obj的强引用,若被回收了,则返回null,所以不可靠,适合做缓存
 }

此时,对于这个Obj对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量softRef 的强引用,所以这个Obj对象是强可达的,所以softRef.get()永远是有返回值的。此加如下代码:

  ...省略上面代码...
  obj = null; //删除掉强引用

这时,Obj对象就只剩下软引用了,是软可达的。这个时候如果用get(),返回值就有可能是null了,因此使用的时候要十分注意。但是Obj对象被回收后又出现问题:SoftReference成垃圾了,完全没用了,这个时候建议处理掉,否则可能造成内存泄漏现象。所以ReferenceQueue队列上场啦。

对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,并且放进ReferenceQueue里(更详细的可咨询JVM的对象回收流程),so

 public static void main(String[] args) {
    //定义一个引用队列
    ReferenceQueue<Obj> queue = new ReferenceQueue<>();
    //创建一个强引用
    Obj obj = new Obj();
    //创建一个软引用,并且关联上引用队列
    SoftReference<Obj> softRef = new SoftReference<>(obj, queue);
    if ((softRef = (SoftReference<Obj>) queue.poll()) != null) {
        //队列里存在 说明对象马上就要被回收了 所以顺势也把软引用对象干掉
        softRef = null; //可参考expungeStaleEntries方法
    }
}

从上可以看出,咱们就可以监听回收,然后doSomething了

弱引用(WeakReference):弱引用和软引用很像,当gc时,无论内存是否充足,都会回收被弱引用关联的对象。它也可以和ReferenceQueue配合使用。如果弱引用所引用的对象被JVM回收,这个弱引用就会被加入到与之关联的引用队列中

虚引用(关注使用场景)

虚引用(PhantomReference):虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期(java对象的生命周期)。一个对象与虚引用关联,则跟没有引用与之关联一样,所以get()方法永远返回null,在任何时候都可能被垃圾回收器回收。因此它必须和ReferenceQueue一起使用,否则没有任何意义

3、使用场景

  • 使用软引用构建敏感数据的缓存(如用户的基本信息)
  • 使用弱引用构建非敏感数据的缓存,如WeakHashMap 当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。

如果使用全局Map,必然会造成很大程度上的内存泄漏。鉴于软引用(弱引用)的特点,可以结合ReferenceQueue来实现高速缓存了,这样对内存也特别友好。下面介绍一个实例演示,让同学们有个感官上的认识

 public static void main(String[] args) {
    Map<Object, Object> map = new HashMap<>();
    //放置1000个1M大小的对象
    for (int i = 0; i < 1000; i++) {
        byte[] bytes = new byte[1024 * 1024 * 8];
        map.put(i, bytes);
    }
    //报错:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    System.out.println("map.size->" + map.size()); 
}

上面使用了强引用类型,就直接报错了,这是必然的OOM错误

Map<Object, Object> map = new HashMap<>();
for(int i = 0;i < 10000;i++) {
    byte[] bytes = new byte[_1M];
    WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);
    map.put(weakReference, i);
}
System.out.println("map.size->" + map.size());

这里使用了weakReference对象,即当值不再被引用时,相应的数据被回收。另外使用一个守护线程不断地从队列中获取被gc的数据

Thread thread = new Thread(() -> {
    try {
        int cnt = 0;
        WeakReference<byte[]> k;
        while((k = (WeakReference) referenceQueue.remove()) != null) {
            System.out.println((cnt++) + "回收了:" + k);
        }
    } catch(InterruptedException e) {
        //结束循环
    }
});
thread.setDaemon(true);
thread.start();

结果如下:

9992回收了:java.lang.ref.WeakReference@1d13cd4
9993回收了:java.lang.ref.WeakReference@118b73a
9994回收了:java.lang.ref.WeakReference@1865933
9995回收了:java.lang.ref.WeakReference@ad82c
map.size->10000

在这次处理中,map并没有因为不断加入的1M对象而产生OOM异常,并且最终size=10000。不过其中的key(即weakReference)对象中的byte[]对象却被回收了。即不断new出来的1M数组被gc掉了。
从打印的结果中,我们看到有9995个对象被gc回收了,意味着在map的key中,除了weakReference之外,没有我们想要的业务对象(只存在引用对象,不存在真实对象了)。所以这个时候为了节约内存,其实是可以把entry一起移除掉的,这里不做演示了,同学们可以自行试验

4、最后

咱们最常用的肯定是强引用,但是java提供的另外几种引用类型也是很有必要了解的,在特殊的场合也非常好用,特别是对于内存敏感的一些业务常用,可以极大的提高内存使用率提升效率,也可以提升jvm的能力
【小家java】引用类型(强引用、软引用、弱引用、虚引用)_第1张图片
##—-题后语—-
我的微信公众号也会持续推送技术干货,欢迎下方二维码扫码关注获取。
【小家java】引用类型(强引用、软引用、弱引用、虚引用)_第2张图片
更多内容持续更新中,欢迎关注我的博客!
有任何问题,可以跟我留言讨论,欢迎指正,不胜感激。
有任何疑问,亦可扫码向我提我哟~

你可能感兴趣的:(享学Java)