Java 强引用、软引用、弱引用、虚引用及回收机制简单解析

0x0001 什么是引用

在 JDK1.2 以前对引用的定义为:

如果 reference 类型的数据中存储的数值代表另外一块内存的起始地址,那么就称这块内存代表一个引用。

此定义将一个对象分为被引用或者没有被引用的两种状态,但是如果内存不足时,被引用的对象由于不能被释放,自然就会导致 OOM。所以需要定义一类这样的对象:当内存空间足够时,对象继续保存在内存中,如果内存不够时,可以回收这些对象。

所以在 JDK1.2 之后就对 Java 引用的概念进行了扩充,将引用细分为:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

0x0002 不同引用类型的回收机制

强引用

永远不会被回收,即使系统 OOM 也不会被回收,就是这么刚。通过 new 关键字创建的实例对象为强引用,对于这样的对象如果没有其他引用关系,只要超过了引用的作用域或者显式的将相应的引用赋值为 null,就能够被 GC 回收了,不过具体什么时候回收,由系统来决定。

软引用(SoftReference)

用来描述一些还有用并非必需的对象。对于软引用的关联的对象,在系统将要发生 OOM 之前,将会把这些对象列入回收范围内,进行第二次回收,如果此时内存还是不够,才会报出 OOM。软引用通常用来实现内存敏感的内存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时才会清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

弱引用(WeakReference)

弱引用也用于描述非必需的对象。它的强度比弱引用更弱一些,被软引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时(执行 GC),无论当前的内存是否够用,都会回收掉只被弱引用关联的对象

弱引用仅仅是提供 一种访问在弱引用状态下对象的途径,这样就可以构建一种没有特定约束的关系,比如维护一种非强制的映射关系,如果试图获取时该对象还在,就是用它,否则就重新实例化。和软引用一样,弱引用也是很多缓存实现的选择。

虚引用(PhantomReference)

虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用获取一个对象实例。如果一个对象只有一个虚引用,那它和没有引用效果大致相同,为一个对象设置虚引用关联的唯一目的就是能在这个对象被垃圾回收器回收时收到一个系统通知

虚引用不像软引用和弱引用可以单独使用,主要用于跟踪对象被垃圾回收的状态,必须和引用队列一起使用。应用程序通过检查与虚引用关联的引用队列是否包含指定的虚引用,从而了解虚引用所引用的对象是否即将会被回收。

0x0003 Reference

所有的引用类型,都是抽象类 java.lang.ref.Reference 的子类,它提供了 get() 方法,除了幻象引用( get() 获得的永远是 null),如果对象还没有被销毁,都可以通过 get 方法获得原有的对象,这就意味着,可以利用软引用和弱引用将访问的对象重新指向强引用,所以引用类型之间是可以转换的。

但是如果我们将通过弱引用或软引用获得对象 (get() 方法) 指向错误的强引用(比如说 static 变量),那么这个对象就不可能再变回之前的弱引用或软引用的状态的了,就导致产生内存泄漏,所以 检查弱引用指向的对象有没有被 GC,这是检查特定对象是否引起内存泄漏的思路。

0x0004 引用队列(ReferenceQueue)的使用

使用SoftReference,WeakReference,PhantomReference 的时候,可以关联一个ReferenceQueue。那么当垃圾回收器回收(虚引用在准备回收时)一个被引用包装的对象时,该引用会被加入到关联的 ReferenceQueue。程序可以通过判断引用队列中是否已经加入引用,来了解被引用的对象是否被 GC 回收,从而对原对象进行一些处理,具体示例,参见文末的知识链接。


知识链接:

ReferenceQueue的使用