什么是强引用、软引用、弱引用和虚引用?

#Java#面试八股

什么是强引用、软引用、弱引用和虚引用?

结论先行:四种引用是从强到弱的可达性等级,决定了对象「什么时候可以被回收」:**强引用(strong reference)**永不主动回收,宁可 OOM;**软引用(soft reference)**只在内存不足时回收,适合做缓存;**弱引用(weak reference)**只要发生 GC 就回收,适合做非强制的关联;**虚引用(phantom reference)**不决定生命周期,只用于在对象被回收时收到通知,配合 ReferenceQueue 做资源清理。

一、四者对照

引用类型回收时机能否 get() 到对象典型用途
强引用永不主动回收,除非不可达能普通 new 出来的对象
软引用内存不足、即将 OOM 前未回收前能图片/页面缓存
弱引用下次 GC 时,无论内存是否充足未回收前能ThreadLocal 的 key、WeakHashMap
虚引用随时可能被回收,get() 永远返回 null不能回收通知、堆外内存释放
强 ─── 软 ─── 弱 ─── 虚
 ↑                    ↑
 最难回收            最易回收

强度越弱,越早被回收;GC Roots 可达性相同时,按引用强度决定去留。

二、软引用:内存敏感的缓存

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;

public class SoftCache {
    private final Map<String, SoftReference<byte[]>> cache = new HashMap<>();

    public byte[] get(String key, java.util.function.Supplier<byte[]> loader) {
        SoftReference<byte[]> ref = cache.get(key);
        byte[] value = ref == null ? null : ref.get();
        if (value == null) {                       // 已被回收或从未加载
            value = loader.get();
            cache.put(key, new SoftReference<>(value));
        }
        return value;
    }

    public static void main(String[] args) {
        SoftCache c = new SoftCache();
        System.out.println(c.get("k1", () -> new byte[1024]).length);   // 1024
    }
}

软引用在「真正 OOM 之前」被清理,因此它能撑住缓存命中率而不至于直接把进程打死。缺点是回收策略不可控,GC 行为的微小变化就会导致缓存雪崩,线上缓存更推荐 Caffeine 这类带容量上限的实现。

三、弱引用:不阻止回收的关联

import java.lang.ref.WeakReference;

public class WeakDemo {
    public static void main(String[] args) {
        Object strong = new Object();
        WeakReference<Object> weak = new WeakReference<>(strong);
        System.out.println(weak.get() != null);   // true,强引用还在

        strong = null;                            // 断开强引用
        System.gc();
        System.out.println(weak.get() == null);   // true,只剩弱引用,GC 后清空
    }
}

WeakHashMap 是弱引用的经典应用:key 是弱引用,一旦外部不再持有 key,整个 entry 就可以回收,非常适合做「附加在某个对象上的元数据」。ThreadLocalMap 的 key 也是弱引用,但 value 是强引用,所以线程池里必须显式 remove(),否则 value 泄漏。

四、虚引用与清理机制

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class PhantomDemo {
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        byte[] resource = new byte[1024 * 1024];
        PhantomReference<byte[]> phantom = new PhantomReference<>(resource, queue);

        System.out.println(phantom.get());        // 永远 null

        resource = null;
        System.gc();
        Reference<?> polled = queue.remove(2000); // 回收后入队,收到通知
        System.out.println(polled != null ? "收到回收通知,可释放堆外资源" : "未收到");
    }
}

DirectByteBuffer 的堆外内存释放就依赖这套机制:JDK 9 起用 Cleaner(内部组合了虚引用与 ReferenceQueue),在对象被回收时执行 Unsafe.freeMemory。

五、常见追问

追问回答要点
软引用什么时候被清理内存不足时;-XX:SoftRefLRUPolicyMSPerMB 控制「每 MB 空闲堆保留多久」,不建议随意调
弱引用会不会一创建就没了不会,创建时若还有强引用就存活;只有强引用消失后,下一次 GC 才清理
虚引用为什么 get() 返回 null设计上就不允许通过虚引用访问对象,它只负责「回收后通知」
finalize 与四种引用的关系finalize 是在对象不可达后的一次自救机会;引用类型决定「是否可达」,两者层次不同
缓存到底该用哪种弱引用适合「有则用、无则重建」;容量受限的业务缓存用 Caffeine/Guava,别手写软引用

小结

强度决定去留:强引用保命,软引用扛内存压力,弱引用随时可清,虚引用只做通知。记住各自「绑定」的场景,比背定义更有用。

笔记加载中…