SoftReference在JVM抛出OutOfMemoryError前尽可能多地回收,具体由GC策略和堆空闲比率共同决定,非实时响应内存压力,适合内存敏感缓存但生命周期不可控。

SoftReference 什么时候会被回收
SoftReference 的对象不是“内存不够就立刻回收”,而是 JVM 在抛出 OutOfMemoryError 之前,**尽可能多地清理所有软引用**。具体时机取决于 JVM 实现(HotSpot 中由 GC 策略和堆剩余空间共同决定),但关键点是:它比 WeakReference 更“懒”,适合做缓存。
常见误判是以为“只要内存紧张就清”,结果发现缓存长期不释放——这往往是因为当前堆还有足够空闲空间,或 GC 尚未触发 Full GC。你可以通过 -XX:SoftRefLRUPolicyMSPerMB=1000 调整策略(单位毫秒/MB),让 JVM 更激进地回收:每 MB 堆空闲空间对应最多保留软引用 1000ms。
用 SoftReference 包装缓存值的正确姿势
别直接把原始对象塞进 SoftReference,尤其当缓存键是字符串或小对象时,容易因引用链过短导致提前回收。推荐结构是:
- 缓存容器用
ConcurrentHashMap<k softreference>></k>,避免同步开销 - 每次
get()后必须检查ref.get() == null,因为 GC 可能在任意时刻发生 - 不要在构造
SoftReference时传入 null,否则get()永远返回 null
示例:
立即学习“Java免费学习笔记(深入)”;
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
private final ConcurrentHashMap<String, SoftReference<ExpensiveObject>> cache = new ConcurrentHashMap<>();
public ExpensiveObject get(String key) {
SoftReference<ExpensiveObject> ref = cache.get(key);
if (ref != null) {
ExpensiveObject obj = ref.get(); // 注意:这里可能为 null
if (obj != null) return obj;
}
ExpensiveObject newObj = createExpensiveObject(key);
cache.put(key, new SoftReference<>(newObj));
return newObj;
}
为什么不能用 SoftReference 当 Map 的 key
SoftReference 本身不能作为 HashMap 或 ConcurrentHashMap 的 key——因为它的 hashCode() 和 equals() 是基于引用对象的,而软引用对象一旦被回收,get() 返回 null,后续无法再通过 key 查找对应 entry,造成“内存泄漏式缓存”:key 还在 map 里,value 却丢了,且无法清理。
真正安全的做法只有两种:
- 用普通对象(如
String)作 key,SoftReference只包 value - 如果真需要 key 也软引用,改用
WeakHashMap(但它是弱引用,生命周期更短,不适合缓存)
和 WeakReference、PhantomReference 对比的关键取舍
三者不是“升级关系”,而是适用场景完全不同:
-
SoftReference:适合内存敏感型缓存,比如图片、JSON 解析结果,允许延迟释放 -
WeakReference:适合临时绑定(如监听器解绑)、或生命周期严格跟随某个对象(如WeakHashMap的 key) -
PhantomReference:仅用于跟踪对象已被 finalize 且即将回收,不能取值,必须配合ReferenceQueue使用
一个常被忽略的细节:SoftReference 不会阻止其 referent 的 finalize 方法执行;如果对象重写了 finalize(),且没被及时回收,可能导致软引用长期滞留——所以现代 Java(9+)已弃用 finalize(),建议用 Cleaner 替代。

















