一、引子:为什么需要不同的引用类型?
想象你家的冰箱就是JVM的内存空间。强引用像是每天必吃的饭菜,软引用像偶尔解馋的零食,弱引用像随时可能被清理的过期食品,虚引用则像食品包装上的标签——东西没了但还能知道它存在过。Java设计这四种引用类型,本质上是为了更精细地控制内存使用。
二、强引用:最普通的"钉子户"
// 示例1:强引用典型用法(技术栈:Java 8+)
public class StrongReferenceDemo {
public static void main(String[] args) {
// 强引用:只要引用存在,对象绝不会被回收
Object vitalObject = new Object(); // ① 强引用建立
System.gc(); // ② 主动触发GC
// 即使内存不足,强引用对象也不会被回收
System.out.println("GC后强引用对象仍然存在:" + vitalObject);
// 取消强引用(置为null)
vitalObject = null; // ③ 取消强引用
System.gc();
System.out.println("取消引用后对象可能已被回收");
}
}
强引用就像租房时的长期合同,除非你主动退租(置为null),否则房东(GC)绝不会赶走你的物品(对象)。实际开发中,我们90%的代码都在使用强引用。
典型场景:
- 核心业务对象(如订单、用户信息)
- 长期存活的配置信息
- 需要严格生命周期控制的对象
注意事项:
- 内存泄漏的主要来源就是忘记释放强引用
- 集合类中的对象如果没有及时移除,会一直保持强引用
三、软引用:内存不足时的"妥协者"
// 示例2:软引用实战(技术栈:Java 8+)
import java.lang.ref.SoftReference;
public class SoftReferenceDemo {
public static void main(String[] args) {
// 创建大对象(模拟缓存)
byte[] imageData = new byte[1024 * 1024 * 10]; // 10MB图片数据
// 建立软引用
SoftReference<byte[]> softRef = new SoftReference<>(imageData);
// 取消强引用,只保留软引用
imageData = null;
// 第一次GC可能不会回收
System.gc();
System.out.println("第一次GC后:" + (softRef.get() != null ? "存活" : "回收"));
// 模拟内存不足(连续申请内存触发GC)
try {
byte[] memoryHog = new byte[1024 * 1024 * 15]; // 申请15MB
} catch (OutOfMemoryError e) {
System.out.println("内存不足时软引用状态:" +
(softRef.get() != null ? "存活" : "回收"));
}
}
}
软引用就像你放在储物间的备用物品,当家里空间充足时它们安然无恙,但当你需要腾出空间时,它们会首先被清理。Android的图片缓存就大量使用这种机制。
最佳实践:
- 适合实现内存敏感的缓存
- 建议配合ReferenceQueue使用(后文会介绍)
- 缓存大小应该根据可用内存动态调整
四、弱引用:GC时的"墙头草"
// 示例3:弱引用演示(技术栈:Java 8+)
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) {
Object tempObject = new Object();
// 建立弱引用
WeakReference<Object> weakRef = new WeakReference<>(tempObject);
System.out.println("强引用存在时:" + weakRef.get());
// 取消强引用
tempObject = null;
// 即使内存充足,GC也会回收弱引用
System.gc();
System.out.println("GC后弱引用对象:" +
(weakRef.get() != null ? "存活" : "回收"));
}
}
WeakHashMap是弱引用的经典应用,它特别适合存储临时性的元数据。比如在Spring框架中,就使用弱引用来存储某些临时生成的代理类。
特殊用法:
// 示例4:WeakHashMap使用示例(技术栈:Java 8+)
import java.util.WeakHashMap;
public class WeakHashMapDemo {
public static void main(String[] args) {
WeakHashMap<Object, String> weakMap = new WeakHashMap<>();
Object key = new Object();
weakMap.put(key, "重要数据");
System.out.println("强引用存在时:" + weakMap.get(key));
key = null; // 取消强引用
System.gc();
System.out.println("GC后WeakHashMap大小:" + weakMap.size());
}
}
五、虚引用:对象终结的"守墓人"
// 示例5:虚引用完整示例(技术栈:Java 8+)
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceDemo {
public static void main(String[] args) {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object phantomObj = new Object();
// 建立虚引用(必须关联ReferenceQueue)
PhantomReference<Object> phantomRef =
new PhantomReference<>(phantomObj, queue);
System.out.println("虚引用get()始终返回null:" + phantomRef.get());
// 取消强引用
phantomObj = null;
System.gc();
// 检查引用队列
if (queue.poll() != null) {
System.out.println("对象已进入finalization阶段");
// 这里可以执行资源清理操作
}
}
}
虚引用就像墓志铭,对象本身已经不存在了,但还能知道它曾经存在过。DirectByteBuffer的清理机制就使用了虚引用,确保native内存被正确释放。
六、引用队列:引用对象的"回收站"
// 示例6:引用队列完整示例(技术栈:Java 8+)
import java.lang.ref.*;
public class ReferenceQueueDemo {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object importantData = new Object();
// 创建弱引用并关联队列
WeakReference<Object> weakRef =
new WeakReference<>(importantData, queue);
// 取消强引用
importantData = null;
System.gc();
// 检查队列(阻塞式)
Reference<?> clearedRef = queue.remove(1000);
if (clearedRef != null) {
System.out.println("引用对象已被回收:" + clearedRef);
// 执行后续清理操作
}
}
}
引用队列就像垃圾回收的"通知中心",当引用对象被回收后,对应的引用会被放入队列,这样我们就可以执行一些后续操作。这在实现高级缓存系统时非常有用。
七、实战:实现智能缓存系统
// 示例7:多级缓存实现(技术栈:Java 8+)
import java.lang.ref.*;
import java.util.HashMap;
import java.util.Map;
public class SmartCache<K, V> {
private final Map<K, V> strongCache = new HashMap<>();
private final Map<K, SoftReference<V>> softCache = new HashMap<>();
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
public void put(K key, V value, boolean isStrong) {
if (isStrong) {
strongCache.put(key, value);
} else {
// 清理已被回收的软引用
processQueue();
softCache.put(key, new SoftReference<>(value, queue));
}
}
public V get(K key) {
// 先从强引用获取
V result = strongCache.get(key);
if (result != null) return result;
// 再从软引用获取
SoftReference<V> softRef = softCache.get(key);
if (softRef != null) {
result = softRef.get();
if (result == null) {
softCache.remove(key); // 已被回收
}
return result;
}
return null;
}
private void processQueue() {
Reference<? extends V> ref;
while ((ref = queue.poll()) != null) {
// 找到对应的key并移除
softCache.entrySet().removeIf(entry ->
entry.getValue() == ref);
}
}
}
这个缓存系统实现了:
- 强引用缓存:存放高频访问数据
- 软引用缓存:存放低频但可能用到的数据
- 自动清理机制:通过ReferenceQueue及时清理失效引用
八、总结与选择指南
引用类型对比表:
| 类型 | 生存时间 | 是否阻止GC | 典型用途 |
|---|---|---|---|
| 强引用 | 只要引用存在 | 是 | 常规对象 |
| 软引用 | 内存不足前 | 否 | 内存敏感缓存 |
| 弱引用 | 下次GC时 | 否 | 临时性元数据存储 |
| 虚引用 | 对象被回收后收到通知 | 否 | 资源清理跟踪 |
选择建议:
- 不确定时先用强引用
- 实现缓存优先考虑软引用
- 需要知道对象何时被回收用虚引用
- 临时性映射关系用弱引用
常见陷阱:
- 误用强引用导致内存泄漏
- 过度依赖软引用导致性能波动
- 忘记处理ReferenceQueue导致引用堆积
评论