在Java开发中,我们通常使用new
关键字创建对象,这些默认都是强引用。但Java实际上提供了四种不同强度的引用类型,它们与JVM的内存管理密切相关。理解这些引用类型,能帮助我们编写更高效、更健壮的Java程序,尤其是在处理内存敏感型应用时。
一、引用类型概览
Java从1.2版本开始引入了java.lang.ref
包,提供了四种引用类型,按引用强度从强到弱排列如下:
引用类型 |
类 |
回收时机 |
典型用途 |
---|---|---|---|
强引用 |
默认 |
永不回收(除非不可达) |
常规对象引用 |
软引用 |
SoftReference |
内存不足时回收 |
内存敏感缓存 |
弱引用 |
WeakReference |
下次GC时回收 |
临时缓存、WeakHashMap |
虚引用 |
PhantomReference |
随时可能回收 |
对象回收跟踪 |
二、强引用(Strong Reference)
定义:我们日常使用的普通对象引用就是强引用。
Object obj = new Object(); // 强引用
特点:
-
只要强引用存在,垃圾收集器就永远不会回收被引用的对象
-
即使内存不足,JVM抛出OOM也不会回收强引用对象
-
显式置为
null
或超出作用域后,对象变为可回收状态
内存泄漏场景:
List<Object> list = new ArrayList<>();
while(true) {
list.add(new Object()); // 强引用导致OOM
}
三、软引用(Soft Reference)
定义:描述一些有用但非必需的对象。
SoftReference<byte[]> softRef = new SoftReference<>(new byte[10 * 1024 * 1024]);
特点:
-
在内存充足时,软引用对象不会被回收
-
当内存不足时(即将OOM前),这些对象会被回收
-
适合实现内存敏感缓存
缓存示例:
public class ImageCache {
private final Map<String, SoftReference<Bitmap>> cache = new HashMap<>();
public void put(String key, Bitmap bitmap) {
cache.put(key, new SoftReference<>(bitmap));
}
public Bitmap get(String key) {
SoftReference<Bitmap> ref = cache.get(key);
return ref != null ? ref.get() : null;
}
}
四、弱引用(Weak Reference)
定义:描述非必需对象,比软引用更弱。
WeakReference<Object> weakRef = new WeakReference<>(new Object());
特点:
-
无论内存是否充足,下次GC时弱引用对象都会被回收
-
常用于实现临时缓存(如WeakHashMap)
-
不会阻止对象被finalize
WeakHashMap示例:
WeakHashMap<Key, Value> map = new WeakHashMap<>();
Key key = new Key();
map.put(key, new Value());
key = null; // 下次GC时,Entry会被自动清除
五、虚引用(Phantom Reference)
定义:最弱的引用,无法通过虚引用获取对象。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
特点:
-
任何时候都可能被回收
-
必须与
ReferenceQueue
配合使用 -
主要用于对象回收跟踪(如NIO的DirectBuffer清理)
资源清理示例:
public class ResourceHolder {
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
private final List<PhantomReference<Object>> refs = new ArrayList<>();
public void register(Object resource, Runnable cleanup) {
PhantomReference<Object> ref = new PhantomReference<>(
resource, queue) {
@Override
public void clear() {
cleanup.run();
super.clear();
}
};
refs.add(ref);
// 通常会有线程监控queue并处理
}
}
六、引用队列(ReferenceQueue)
软/弱/虚引用都可以关联引用队列,当引用对象被回收时,引用本身会入队:
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> ref = new WeakReference<>(new Object(), queue);
// 监控队列的线程
new Thread(() -> {
while(true) {
try {
Reference<?> ref = queue.remove();
// 执行清理操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
七、实战应用场景
-
缓存实现:
-
一级缓存(强引用) + 二级缓存(软引用)
-
使用WeakHashMap实现临时缓存
-
-
内存泄漏防护:
// 防止监听器导致的内存泄漏 public void addListener(Object listener) { listeners.add(new WeakReference<>(listener)); }
-
资源清理:
// 确保native资源释放 try (DirectBuffer buf = allocateDirect()) { // 使用虚引用跟踪buf }
八、注意事项
-
软引用在客户端和服务端的表现可能不同(某些JVM实现可能更激进)
-
弱引用不适合做缓存(回收太频繁)
-
虚引用必须配合引用队列使用
-
引用对象本身(如SoftReference实例)仍是强引用,需注意管理
总结
Java的四类引用构成了灵活的内存管理机制:
-
强引用:常规对象生命周期控制
-
软引用:"柔性"缓存,内存不足时释放
-
弱引用:不影响GC的临时关联
-
虚引用:对象回收的精确通知
合理使用这些引用类型,可以帮助我们:
-
优化内存使用
-
防止内存泄漏
-
实现高效缓存
-
精确控制资源释放
理解这些引用类型,是成为Java高级开发者的重要一步。在你的项目中,是否遇到过适合使用这些引用类型的场景呢?