Java 四种引用详解:源码级、原理与应用
前言
在 Java 中,引用类型决定了对象如何被垃圾回收器(GC)处理。Java 提供了四种引用类型,每种类型都有特定的使用场景和行为特征。下面我将从源码层面深入解析这四种引用类型。
一、引用类型概览
| 引用类型 | 回收时机 | 是否可获取对象 | 典型应用场景 |
|---|---|---|---|
| 强引用 (Strong Reference) | 永不回收(除非不可达) | 是 | 普通对象创建 |
| 软引用 (Soft Reference) | 内存不足时回收 | 是 | 内存敏感缓存 |
| 弱引用 (Weak Reference) | GC 运行时回收 | 是 | 规范化映射(如 WeakHashMap) |
| 虚引用 (Phantom Reference) | GC 运行时回收 | 否 | 资源清理跟踪 |
二、源码级解析
1. 强引用 (Strong Reference)
源码实现:
// 所有普通对象创建都是强引用
Object obj = new Object();
核心特性:
- 默认引用类型
- 只要强引用存在,对象永远不会被 GC 回收
- 可能导致内存泄漏(当不再需要但仍持有引用时)
应用场景:
public class StrongReferenceExample {
public static void main(String[] args) {
// 创建强引用
Object strongRef = new Object();
// 即使系统内存不足,也不会回收
System.gc();
System.out.println("强引用对象仍然存在: " + strongRef);
// 显式断开引用
strongRef = null;
// 此时对象变为可回收状态
}
}
2. 软引用 (Soft Reference)
源码实现:
import java.lang.ref.SoftReference;
public class SoftReference<T> extends Reference<T> {
// 时间戳,由GC更新
private long timestamp;
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
// GC调用此方法获取时间戳
long getTimestamp() {
return timestamp;
}
// GC调用此方法更新时间戳
void setTimestamp(long time) {
timestamp = time;
}
}
核心特性:
- 在内存不足时(OOM 前)被回收
- 适合实现内存敏感的缓存
- 通过
get()方法可获取对象
应用场景:
public class ImageCache {
private final Map<String, SoftReference<BufferedImage>> cache = new HashMap<>();
public BufferedImage getImage(String path) {
SoftReference<BufferedImage> ref = cache.get(path);
BufferedImage image = (ref != null) ? ref.get() : null;
if (image == null) {
// 从磁盘加载图片
image = loadImageFromDisk(path);
cache.put(path, new SoftReference<>(image));
}
return image;
}
private BufferedImage loadImageFromDisk(String path) {
// 实现图片加载逻辑
}
}
3. 弱引用 (Weak Reference)
源码实现:
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
核心特性:
- 下一次 GC 运行时即被回收
- 不会阻止对象被回收
- 常用于实现规范化映射(如 WeakHashMap)
应用场景:
public class WeakHashMapDemo {
public static void main(String[] args) {
WeakHashMap<Key, Value> map = new WeakHashMap<>();
Key key1 = new Key("key1");
map.put(key1, new Value("value1"));
System.out.println("GC前: " + map.containsKey(key1)); // true
// 移除强引用
key1 = null;
// 触发GC
System.gc();
// 等待GC完成
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("GC后: " + map.isEmpty()); // true
}
static class Key {
String id;
Key(String id) { this.id = id; }
}
static class Value {
String data;
Value(String data) { this.data = data; }
}
}
4. 虚引用 (Phantom Reference)
源码实现:
public class PhantomReference<T> extends Reference<T> {
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
// 虚引用始终返回null
public T get() {
return null;
}
}
核心特性:
- 无法通过
get()获取对象(始终返回 null) - 对象回收时进入引用队列
- 用于资源清理和对象回收跟踪
应用场景:
public class ResourceCleaner {
public static void main(String[] args) {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
List<PhantomReference<Object>> refs = new ArrayList<>();
List<byte[]> bytes = new ArrayList<>();
new Thread(() -> {
while (true) {
PhantomReference<?> ref = (PhantomReference<?>) queue.poll();
if (ref != null) {
System.out.println("对象被回收: " + ref);
// 执行资源清理操作
refs.remove(ref);
}
}
}).start();
// 创建大对象并关联虚引用
for (int i = 0; i < 10; i++) {
byte[] data = new byte[1024 * 1024]; // 1MB
bytes.add(data);
PhantomReference<Object> ref =
new PhantomReference<>(data, queue);
refs.add(ref);
}
// 移除强引用,允许GC回收
bytes = null;
// 触发GC
System.gc();
}
}
三、引用队列 (ReferenceQueue)
所有引用类型都可以关联一个引用队列,用于跟踪对象回收状态:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
// 创建带队列的软引用
SoftReference<Object> softRef =
new SoftReference<>(new Object(), queue);
// 创建带队列的弱引用
WeakReference<Object> weakRef =
new WeakReference<>(new Object(), queue);
// 创建带队列的虚引用
PhantomReference<Object> phantomRef =
new PhantomReference<>(new Object(), queue);
队列工作原理:
- 当引用对象被回收时,引用本身会被加入关联的队列
- 程序可通过轮询队列获取回收通知
- 特别适合虚引用实现资源清理
四、底层原理与 GC 交互
1. 引用状态机
Java 引用在 GC 过程中经历以下状态:
[ Active ] → [ Pending ] → [ Enqueued ] → [ Inactive ]
- Active:新创建状态,引用对象可达
- Pending:将被放入引用队列,位于 pending 列表
- Enqueued:已加入引用队列
- Inactive:从队列中移除,生命周期结束
2. GC 处理流程
- GC 标记阶段识别可达对象
- 根据引用类型处理不同引用:
- 强引用:保留对象
- 软引用:内存不足时清除
- 弱引用:直接清除
- 虚引用:标记为可回收
- 将待处理的引用加入 pending 列表
- ReferenceHandler 线程将引用加入队列
3. ReferenceHandler 线程
在 Reference 类中有一个专门的线程处理引用队列:
private static class ReferenceHandler extends Thread {
public void run() {
while (true) {
processPendingReferences();
}
}
private static void processPendingReferences() {
// 处理 pending 列表中的引用
// 将它们加入各自的引用队列
}
}
五、最佳实践与应用场景
-
缓存实现
- 软引用:适合内存敏感缓存(如图片缓存)
- 弱引用:适合次要数据缓存
-
资源清理
public class ResourceWithCleanup { private final Resource resource; private final CleanupReference cleanupRef; public ResourceWithCleanup() { this.resource = createResource(); this.cleanupRef = new CleanupReference( resource, new CleanupTask(resource)); } private static class CleanupReference extends PhantomReference<Resource> { private final Runnable cleanupTask; CleanupReference(Resource referent, Runnable cleanupTask) { super(referent, QUEUE); this.cleanupTask = cleanupTask; } void clean() { cleanupTask.run(); } } // 处理队列的守护线程 static { Thread cleaner = new Thread(() -> { while (true) { try { CleanupReference ref = (CleanupReference) QUEUE.remove(); ref.clean(); } catch (InterruptedException e) { // 处理中断 } } }); cleaner.setDaemon(true); cleaner.start(); } } -
防止内存泄漏
public class ListenerManager { private final Map<Object, WeakReference<Listener>> listeners = new WeakHashMap<>(); public void addListener(Listener listener) { listeners.put(listener, new WeakReference<>(listener)); } public void notifyListeners() { listeners.entrySet().removeIf(entry -> entry.getValue().get() == null); for (WeakReference<Listener> ref : listeners.values()) { Listener listener = ref.get(); if (listener != null) { listener.onEvent(); } } } }
六、常见问题与陷阱
-
错误:认为软引用是弱引用的"强"版本
- 实际上软引用在内存充足时行为类似强引用
- 弱引用在任何 GC 周期都可能被回收
-
错误:过度依赖引用队列
- 引用加入队列的时间点不确定
- 不应在关键路径上依赖队列通知
-
最佳实践:
- 软引用适合缓存大对象
- 弱引用适合元数据存储
- 虚引用只用于资源清理
- 强引用是默认选择,但要注意及时释放
-
调试技巧:
// 查看引用队列状态 public static void monitorReferenceQueue(ReferenceQueue<?> queue) { new Thread(() -> { while (true) { try { Reference<?> ref = queue.remove(); System.out.println("Reference dequeued: " + ref); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); }
理解 Java 的四种引用类型对于编写高性能、内存友好的应用程序至关重要。合理使用这些引用类型可以帮助开发者有效管理内存,防止内存泄漏,并实现高效的对象生命周期管理。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120641

浙公网安备 33010602011771号