专栏首页BAT的乌托邦【小家java】引用类型(强引用、软引用、弱引用、虚引用)

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

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的能力

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 聊聊Java的引用类型(强引用、软引用、弱引用、虚引用),示例WeakHashMap的使用【享学Java】

    Java语言中的数据类型可划分为值类型和引用类型。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。 这4种级别由...

    YourBatman
  • [享学Netflix] 二十九、Hystrix执行过程核心接口

    代码下载地址:https://github.com/f641385712/netflix-learning

    YourBatman
  • Fastjson到了说再见的时候了

    各位小伙伴大家好,我是A哥。停更1个月后回归啦,今天咱们聊聊一个比较有意思的话题:是否真的需要跟Fastjson说再见了?

    YourBatman
  • 强引用,软引用,弱引用,幻象引用有什么区别?

    不同的引用类型,主要体现的是对象的不同的可达性(reachable)状态和对垃圾收集的影响。

    王小明_HIT
  • 强引用、软引用、弱引用、虚引用的对比

    从Jdk1.2开始,在java.lang.ref包下就提供了三个类:SoftReference(软引用),PhantomReference(虚引用)和WeakR...

    黑洞代码
  • 说说Java的四大引用

    从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

    黄泽杰
  • 七、引用(reference)详解

    如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。引用不等同于对象本身,根据虚拟机种类的不同,可能是一个指...

    栋先生
  • 你会使用软引用和弱引用吗?

    这篇文章我们来聊聊软引用和弱引用对内存性能的帮助,大家在平时的开发过程中,对于内存性能做过哪些调优工作,其中的一个方法就是我们可以使用软引用和弱引用。

    故里
  • 详解 Java 中的四种引用

    在 Java 中,引用随处可见,我们通过类似 Object obj = new Object(); 的代码就可以创建一个引用,而我们直接通过这个代码段创建的引用...

    指点
  • 【必知必会】深入解析强引用、软引用、弱引用、幻象引用

    关于强引用、软引用、弱引用、幻象引用的区别,在BAT这样大公司的面试题中也经常出现,可能有些小伙伴觉得这个知识点比较冷门,但其实大家在开发中经常用到,如new一...

    猿人谷

扫码关注云+社区

领取腾讯云代金券