四种引用类型

候选人小钱在面试京东 P6 时,面试官问道:

"Java 有哪几种引用类型?它们分别在什么时候被回收?"

小钱说:"有强引用、软引用、弱引用、虚引用..."面试官追问:"WeakHashMap 什么时候清理过期 Entry?"

小钱答不上来。面试官继续追问:"虚引用的 get() 返回什么?"

小钱彻底卡住了...

一、核心问题:四种引用类型 🔴

1.1 问题拆解

第一层:四种引用(有哪些?)
  "JDK 1.2 引入的四种引用类型是什么?"
  考察点:强/软/弱/虚引用

第二层:回收时机(什么时候?)
  "每种引用分别在什么时候被垃圾回收?"
  考察点:OOM 时回收/GC 时回收/无法获取

第三层:使用场景(怎么用?)
  "四种引用各自的典型使用场景是什么?"
  考察点:缓存/规范化映射/堆外内存

1.2 ❌ 错误示范

候选人原话 A:"软引用在内存不足时会被回收,所以可以无限存数据。"

问题诊断:软引用在 OOM 前被回收,但回收后可能仍然 OOM。软引用适合做缓存,但不是无限缓存。

候选人原话 B:"虚引用和弱引用的回收时机是一样的。"

问题诊断:弱引用在下一次 GC时回收;虚引用不影响 GC 回收,只是回收时收到通知。

1.3 标准回答

P5 级别:四种引用

四种引用的回收时机

引用类型回收时机get() 返回值JDK 引入版本
强引用永不回收(直到无引用)对象本身JDK 1.0
软引用OOM 前,内存不足时回收(第一次 GC 不回收)对象或 nullJDK 1.2
弱引用下一次 GC 时回收对象或 nullJDK 1.2
虚引用不影响回收,回收时收到通知永远 nullJDK 1.2

虚引用的特殊行为

PhantomReference<Object> ref = new PhantomReference<>(obj, referenceQueue);
ref.get();  // 永远返回 null

// 但当 obj 被 GC 时,ref 会被加入 referenceQueue
// 另一个线程从 queue 中取出 ref,执行清理逻辑

P6 级别:使用场景

软引用:缓存

// 软引用缓存:内存不足时回收
import java.lang.ref.SoftReference;

Map<String, SoftReference<Bitmap>> cache = new HashMap<>();

Bitmap loadBitmap(String key) {
    SoftReference<Bitmap> ref = cache.get(key);
    if (ref != null) {
        Bitmap bitmap = ref.get();
        if (bitmap != null) return bitmap;  // 缓存命中
    }
    // 缓存未命中或已回收
    Bitmap bitmap = loadFromDisk(key);
    cache.put(key, new SoftReference<>(bitmap));
    return bitmap;
}

弱引用:规范化映射

// WeakHashMap:key 为弱引用
WeakHashMap<Key, Value> map = new WeakHashMap<>();
Key key = new Key();
map.put(key, value);
// key 不再被其他对象引用时,Entry 自动被清理
// 无需手动 remove()

虚引用:堆外内存管理

// DirectByteBuffer 的堆外内存通过 Cleaner 释放
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);  // 分配堆外内存
// buffer 对象本身在堆中
// 实际的堆外内存通过 Cleaner 在 GC 后释放

// Cleaner 底层使用虚引用:
// DirectByteBuffer + Cleaner + PhantomReference

P7 级别:Reference 队列机制

Reference 队列的工作原理

// 创建时关联 ReferenceQueue
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> ref = new SoftReference<>(obj, queue);

// ReferenceHandler 线程(JVM 内部)
// 当对象被 GC 时,Reference 被加入关联的 queue

// 应用线程从 queue 中取出 Reference
Reference<?> ref = queue.remove();
if (ref instanceof SoftReference) {
    // 软引用被回收了,执行清理逻辑
}

SoftReference vs WeakReference 的差异

SoftReference 的回收时机取决于:

  • 剩余物理内存量
  • JVM 的 -XX:SoftRefLRUPolicyMSPerMB 参数(默认 1000ms)

这意味着软引用在还有大量可用内存但即将不足时才会被回收。

【面试官心理】 这道题我能问到 P7 级别,是因为四种引用类型涉及了 JVM GC 机制、内存管理和资源清理的多个维度。能说清 Cleaner 底层使用虚引用的候选人说明他理解了 Java NIO 的堆外内存管理。

1.4 追问升级

追问:FinalReference 和 finalize() 的关系?

FinalReference 是 JVM 内部使用的引用类型,用于支持 Object.finalize() 方法。当对象重写了 finalize() 时,JVM 会创建一个 FinalReference 持有该对象。GC 时,FinalReference 先于普通引用被处理,执行 finalize() 方法。

二、生产避坑 🟡

2.1 WeakHashMap 的 key 误用

// 错误:使用 String 字面量作为 key
WeakHashMap<String, Object> map = new WeakHashMap<>();
map.put("key", value);  // "key" 是字符串常量,存在于字符串常量池,永远可达,不会被回收

// 正确:使用 new String()
WeakHashMap<String, Object> map = new WeakHashMap<>();
map.put(new String("key"), value);  // String 对象在堆中,可被回收

2.2 软引用缓存的空间估算

软引用缓存在内存接近 OOM 时才回收,无法精确控制大小。

💡

面试加分点:能说出"JDK 9 引入了 java.lang.ref.Cleaner,替代了 PhantomReference + 手动 ReferenceQueue 的繁琐模式,提供更简洁的 API",说明他关注了 JDK 9 的改进。

⚠️

面试陷阱:被问到"虚引用可以用来缓存吗",很多人会说"可以"。准确答案是:不能。虚引用的 get() 永远返回 null,无法通过它访问对象。虚引用只能用于在对象被 GC 时收到通知。