文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

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);

队列工作原理

  1. 当引用对象被回收时,引用本身会被加入关联的队列
  2. 程序可通过轮询队列获取回收通知
  3. 特别适合虚引用实现资源清理

四、底层原理与 GC 交互

1. 引用状态机

Java 引用在 GC 过程中经历以下状态:

[ Active ] → [ Pending ] → [ Enqueued ] → [ Inactive ]
  • Active:新创建状态,引用对象可达
  • Pending:将被放入引用队列,位于 pending 列表
  • Enqueued:已加入引用队列
  • Inactive:从队列中移除,生命周期结束

2. GC 处理流程

  1. GC 标记阶段识别可达对象
  2. 根据引用类型处理不同引用:
    • 强引用:保留对象
    • 软引用:内存不足时清除
    • 弱引用:直接清除
    • 虚引用:标记为可回收
  3. 将待处理的引用加入 pending 列表
  4. ReferenceHandler 线程将引用加入队列

3. ReferenceHandler 线程

在 Reference 类中有一个专门的线程处理引用队列:

private static class ReferenceHandler extends Thread {
    public void run() {
        while (true) {
            processPendingReferences();
        }
    }
    
    private static void processPendingReferences() {
        // 处理 pending 列表中的引用
        // 将它们加入各自的引用队列
    }
}

五、最佳实践与应用场景

  1. 缓存实现

    • 软引用:适合内存敏感缓存(如图片缓存)
    • 弱引用:适合次要数据缓存
  2. 资源清理

    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();
        }
    }
    
  3. 防止内存泄漏

    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();
                }
            }
        }
    }
    

六、常见问题与陷阱

  1. 错误:认为软引用是弱引用的"强"版本

    • 实际上软引用在内存充足时行为类似强引用
    • 弱引用在任何 GC 周期都可能被回收
  2. 错误:过度依赖引用队列

    • 引用加入队列的时间点不确定
    • 不应在关键路径上依赖队列通知
  3. 最佳实践:

    • 软引用适合缓存大对象
    • 弱引用适合元数据存储
    • 虚引用只用于资源清理
    • 强引用是默认选择,但要注意及时释放
  4. 调试技巧:

    // 查看引用队列状态
    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 的四种引用类型对于编写高性能、内存友好的应用程序至关重要。合理使用这些引用类型可以帮助开发者有效管理内存,防止内存泄漏,并实现高效的对象生命周期管理。

posted @ 2025-09-13 11:59  NeoLshu  阅读(3)  评论(0)    收藏  举报  来源