Java的四种引用类型

Reference

Posted by MistRay on April 13, 2020

前言

上一篇文章我们讨论了ThreadLocal相关的问题,其中内存泄漏的原因是因为Thread对象内部维护的ThreadLocalMap,这个Map的Key是弱引用类型(WeakReference),而Value是强引用类型,如果Key被回收,Value却不会被回收。本期让我们详细分析一下Java的四种引用。

Java的引用

从 JDK1.2 开始,对象的引用从原来的1种级别增加到了4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用
Java.lang.ref 是 Java 类库中比较特殊的一个包,它提供了与 Java垃圾回收器密切相关的引用类。StrongReference(强引用),SoftReference(软引用),WeakReference(弱引用),PhantomReference(虚引用)。这四种引用的强度按照上面的顺序依次减弱。

引用类图

引用类图

强引用(StrongReference)为JVM内部实现。其他三类引用类型全部继承自Reference父类。

强引用

最常用到的引用类型,StrongReference这个类并不存在,而是在JVM底层实现。默认的对象都是强引用类型,继承自Reference、SoftReference、WeakReference、PhantomReference的引用类型非强引用。
示例:

String str = "MistRay";

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

软引用

软引用是一种比强引用生命周期稍弱的一种引用类型。在JVM内存充足的情况下,软引用并不会被垃圾回收器回收,只有在JVM内存不足的情况下,才会被垃圾回收器回收。 所以软引用的这种特性,一般用来实现一些内存敏感的缓存,只要内存空间足够,对象就会保持不被回收掉,比如网页缓存、图片缓存等。
示例:

SoftReference<String> softReference = new SoftReference<String>(new String("MistRay"));
System.out.println(softReference.get());

弱引用

弱引用是一种比软引用生命周期更短的引用。他的生命周期很短,不论当前内存是否充足,都只能存活到下一次垃圾收集之前。 不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用, 如果弱引用所引用的对象被垃圾回收, Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
示例:

WeakReference<String> weakReference = new WeakReference<String>(new String("MistRay"));
System.gc();
Thread.sleep(20000);
if(weakReference.get() == null) {
    System.out.println("weakReference已经被GC回收");
}

执行结果大概率为:

weakReference已经被GC回收

示例中有一个细节,如果不加Thread.sleep(20000);这段代码,WeakReference大概率不会被回收,因为System.gc();仅仅是提醒JVM该执行垃圾回收了。 但JVM具体什么时候执行,并不由我们的程序控制,所以在发起提醒后,要sleep一段时间等待JVM发生垃圾回收。

虚引用

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue) 联合使用。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

PhantomReference<String> phantomReference = new PhantomReference<String>(new String("MistRay"), new ReferenceQueue<String>());
System.out.println(phantomReference.get());

上面程序执行的结果一直是null,和没有引用几乎一致。

引用类型对比

引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
强引用 直接调用 不回收 可能
软引用 通过 get()方法 视内存情况回收 不可能
弱引用 通过 get()方法 发生gc时回收 不可能
虚引用 无法取得 随时可能被回收 不可能

各种引用的使用场景

我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。

最容易想到的就是缓存了,内存不足时释放部分数据,类似Redis/Ehcache之类的淘汰策略。

下面列出一下JDK/框架中的应用场景:

  • java.util.WeakHashMap - jdk
  • java.util.concurrent.ArrayBlockingQueue - jdk
  • org.springframework.util.ConcurrentReferenceHashMap - spring中大量使用了此缓存,包括spring-BeanUtils

Reference

Java中的四种引用类型(强、软、弱、虚)
Java 中的强引用/软引用/弱引用/虚引用以及 GC 策略
面试官:说说Java对象的四种引用方式
深入理解Java的四种引用类型
Java中的四种引用

转载

本文遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。