欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

你真的懂软引用、弱引用、虚引用吗?

程序员文章站 2024-02-11 21:29:52
...

网上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

    }
}