一、引子:为什么需要不同的引用类型?

想象你家的冰箱就是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%的代码都在使用强引用。

典型场景

  • 核心业务对象(如订单、用户信息)
  • 长期存活的配置信息
  • 需要严格生命周期控制的对象

注意事项

  1. 内存泄漏的主要来源就是忘记释放强引用
  2. 集合类中的对象如果没有及时移除,会一直保持强引用

三、软引用:内存不足时的"妥协者"

// 示例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);
        }
    }
}

这个缓存系统实现了:

  1. 强引用缓存:存放高频访问数据
  2. 软引用缓存:存放低频但可能用到的数据
  3. 自动清理机制:通过ReferenceQueue及时清理失效引用

八、总结与选择指南

引用类型对比表

类型 生存时间 是否阻止GC 典型用途
强引用 只要引用存在 常规对象
软引用 内存不足前 内存敏感缓存
弱引用 下次GC时 临时性元数据存储
虚引用 对象被回收后收到通知 资源清理跟踪

选择建议

  1. 不确定时先用强引用
  2. 实现缓存优先考虑软引用
  3. 需要知道对象何时被回收用虚引用
  4. 临时性映射关系用弱引用

常见陷阱

  1. 误用强引用导致内存泄漏
  2. 过度依赖软引用导致性能波动
  3. 忘记处理ReferenceQueue导致引用堆积