你真的懂软引用、弱引用、虚引用吗?
网上10篇关于软引用、弱引用的文章有8篇是错的。以致于,在谈到ThreadLocal等上层建筑时胡说八道,却并不知道自己在胡说八道。
引用类型分为如下几种:
强引用:普通对象引用。
软引用:如果内存不够就自动清理。(前提是没有强引用指向被引用对象)
弱引用:垃圾回收机制一运行,不管内存够不够,它都会被清理。(前提是没有强引用指向被引用对象)
虚引用:跟踪对象被GC的状态。必须与ReferenceQueue配合使用。
要理解这几种引用,最重要的概念是理解“清理(clear)”这个概念。注意,我在这里绝对没有使用“回收”这个词。
我们来看下面这两句:
Object obj = new Object();
SoftReference sRef = new SoftReference(obj); // No generics, bite me.
执行后,在内存中大体如下:
栈上有两个变量,obj和sRef,分别指向堆里的两个具体对象,注意,这两个引用都是强引用(粗箭头所示),也就是说一个软引用对象和普通对象没什么不同。而堆中的软引用对象对实际对象(referent)的引用,是软引用(细箭头所示)。
“清理”的语义,就是把这个细箭头断开(如图中红叉所示)。“清理”既不是指回收堆里的软引用对象,也不是回收被引用对象即referent,而是切断这个软引用,即切断堆里这两个对象间的联系;使得sRef.get()返回null。
这是重点:“清理”的意思是切断软引用对象与被引用对象的联系,不是任何回收动作本身。
有两种方法“清理”软引用:第一,程序主动调用Reference的clear()函数;第二,当GC“感到”内存可能不足,且referent没有强引用时(此时称作softly reachable),自动切断其联系;如果注册了ReferenceQueue则在切断后将软引用对象入Queue。
WeakReference与SoftReference的唯一不同是它不管内存够不够,下一次GC运行时就执行自动清理(以及入Queue)。
那么,指向referent的所有软引用都被清理后,referent是不是就立刻被回收了?不一定。一方面,可能有强引用还在引用它(比如上面我们没有让obj=null);另一方面,就算再没有别的引用了,如果它覆写了finalize()函数,那么就还要经过finalizable标记、finalization等步骤之后才会被回收,也就是说要等到下一次GC。
至于那个软引用对象,它就是一个普通对象,对它的回收跟其它对象没什么不同,比如令sRef=null,它就变成可回收的了。
Weak和Soft都可以与引用队列配合使用,也可以不。队列是一个工具,用来通知程序:referent不可达了。比如WeakHashMap就用了Queue,而ThreadLocal没有用,他们本质上是一种东西,一种思想的产物。都是将实际Map.Entry里的那个key变为指向put(key, value)的那个key的弱引用(确切地说,Entry本身继承自WeakReference,它指向原始key,value仍作为Entry的属性存在)。这样,当你在Map外面不再有对这个key的强引用了,GC运行时就会执行清理,那个Entry里的key就get到null,此时就要把这个Entry删掉。不同之处在于WeakHashMap用Queue,在任何时候访问这个Map都调用一次ReferenceQueue.poll(),来看是否有null的key,有的话就删掉对应的Entry。而ThreadLocal不使用Queue,当你调用get、set、remove等操作时,会调用一个叫expungeStaleEntry的方法,它会遍历ThreadLocalMap,删掉那些key为null的Entry。
虚引用则是一个很无厘头的东西,它的get方法任何时候都返回null。这意味着它没什么用。唯一作用是当referent处于可回收状态时自己就入Queue,使程序知道有这么回事,以便做些扫尾工作,代替finalize函数;它比finalize函数强的地方在于它永远得不到referent,因此无法将之“复活”。
finalize函数有个悖论问题,那就是,finalize本来是在GC时调用的,但在finalize里却可以复活这个对象,比如anotherObj.someProp=this;一下子把它起死回生了,于是本次GC反而不能回收它。
见https://www.baeldung.com/java-phantom-reference
在JDK9之前,PhantomReference行为与WeakReference和SoftReference都不同,它入Queue前不会自动清理,JDK9之后改成和其它Reference行为一致了。
下面附送一个WeakHashMap验证小程序。演示它的自删。
import java.util.WeakHashMap;
public class WeakHashMapTest {
public static void main(String[] args) throws Exception {
WeakHashMap m = new WeakHashMap();
for(int i=0; i<200; i++) {
Object key = new String("key" + i);
Object val = new Integer(i);
m.put(key, val);
}
System.out.println(m.get("key42")); // 42
System.out.println(m.size()); // 200
// force gc!
System.gc();
System.runFinalization();
m.remove("key42"); // remove() causes enqueue and deleting of entries
System.out.println(m.size()); // 0
}
}
上一篇: Java中的传递机制——值传递