问题

什么是强引用、软引用、弱引用和虚引用?

答案

核心概念

Java提供了四种不同的引用类型:强引用(Strong Reference)软引用(Soft Reference)弱引用(Weak Reference)虚引用(Phantom Reference)。这些引用类型具有不同的强度,在垃圾回收时有不同的行为,为Java程序提供了灵活的内存管理机制。

强引用(Strong Reference)

1. 基本特性

强引用是最常见的引用类型,我们日常使用的99%的引用都是强引用。只要存在强引用指向对象,垃圾回收器就永远不会回收该对象。

public class StrongReferenceExample {
    public static void main(String[] args) {
        // 强引用:普通的对象引用
        Object obj = new Object(); // obj是强引用
        String str = "Hello";      // str是强引用

        // 只要强引用存在,对象就不会被回收
        obj = null; // 解除强引用,对象才可能被回收
        str = null;
    }

    public static void demonstrateStrongReference() {
        List<String> list = new ArrayList<>();

        // list中的元素都是强引用
        list.add("元素1");
        list.add("元素2");

        // 即使list本身被回收,其中的对象引用仍会阻止GC
        list = null; // 只有list和其中所有元素的强引用都解除,对象才能被回收
    }
}

2. 强引用的内存泄漏风险

public class MemoryLeakExample {
    // 静态集合中的强引用可能导致内存泄漏
    private static final Map<String, Object> CACHE = new HashMap<>();

    public static void addToCache(String key, Object value) {
        CACHE.put(key, value); // 强引用,对象永远不会被回收
    }

    // 更好的做法:使用WeakHashMap或定期清理
    public static void memoryLeakScenario() {
        List<Object> objects = new ArrayList<>();
        for (int i = 0; i < 100000; i++) {
            objects.add(new LargeObject()); // 强引用,对象不会被回收
        }
        // 如果不清理objects,会造成内存泄漏
    }
}

软引用(Soft Reference)

1. 基本特性

软引用用来描述一些还有用但非必需的对象。在系统内存充足时,软引用对象不会被回收;只有在内存不足时,才会回收这些对象。

import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;

public class SoftReferenceExample {
    public static void main(String[] args) {
        // 创建软引用
        Object obj = new Object();
        SoftReference<Object> softRef = new SoftReference<>(obj);

        // 通过软引用获取对象
        Object retrievedObj = softRef.get(); // 如果对象未被回收,返回对象
        System.out.println("Retrieved object: " + (retrievedObj != null));

        // 清除强引用,对象只通过软引用引用
        obj = null;

        // 模拟内存不足的情况
        System.gc(); // 可能不会立即回收
        createMemoryPressure(); // 创建内存压力,触发软引用回收

        retrievedObj = softRef.get(); // 可能返回null
        System.out.println("After GC: " + (retrievedObj != null));
    }

    private static void createMemoryPressure() {
        try {
            // 创建大量对象消耗内存
            List<byte[]> memoryConsumer = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                memoryConsumer.add(new byte[1024 * 1024]); // 1MB
            }
        } catch (OutOfMemoryError e) {
            System.out.println("内存不足,软引用可能被回收");
        }
    }
}

2. 软引用的实际应用

public class SoftReferenceApplications {
    // 1. 缓存实现
    public static class SoftCache<K, V> {
        private final Map<K, SoftReference<V>> cache = new HashMap<>();

        public void put(K key, V value) {
            cache.put(key, new SoftReference<>(value));
        }

        public V get(K key) {
            SoftReference<V> ref = cache.get(key);
            return ref != null ? ref.get() : null;
        }

        public void cleanUp() {
            cache.entrySet().removeIf(entry -> entry.getValue().get() == null);
        }
    }

    // 2. 图片缓存
    public static class ImageCache {
        private static final Map<String, SoftReference<byte[]>> imageCache = new HashMap<>();

        public static byte[] getImage(String imagePath) {
            SoftReference<byte[]> ref = imageCache.get(imagePath);
            byte[] imageData = ref != null ? ref.get() : null;

            if (imageData == null) {
                // 从文件系统加载图片
                imageData = loadImageFromFile(imagePath);
                imageCache.put(imagePath, new SoftReference<>(imageData));
            }

            return imageData;
        }

        private static byte[] loadImageFromFile(String path) {
            // 模拟图片加载
            return new byte[1024 * 100]; // 100KB
        }
    }

    // 3. 数据库连接池
    public static class SoftConnectionPool {
        private final Queue<SoftReference<Connection>> pool = new LinkedList<>();

        public Connection getConnection() {
            while (!pool.isEmpty()) {
                SoftReference<Connection> ref = pool.poll();
                Connection conn = ref.get();
                if (conn != null && !conn.isClosed()) {
                    return conn;
                }
            }
            return createNewConnection();
        }

        public void releaseConnection(Connection conn) {
            if (conn != null && !conn.isClosed()) {
                pool.offer(new SoftReference<>(conn));
            }
        }

        private Connection createNewConnection() {
            // 创建新连接
            return null;
        }
    }
}

弱引用(Weak Reference)

1. 基本特性

弱引用的强度比软引用更弱,无论内存是否充足,只要垃圾回收器运行,就会回收只被弱引用引用的对象。

import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;

public class WeakReferenceExample {
    public static void main(String[] args) {
        // 创建弱引用
        Object obj = new Object();
        WeakReference<Object> weakRef = new WeakReference<>(obj);

        System.out.println("Before GC: " + (weakRef.get() != null));

        // 清除强引用
        obj = null;

        // 触发垃圾回收
        System.gc();

        // 弱引用对象被回收
        System.out.println("After GC: " + (weakRef.get() != null));
    }

    // 使用ReferenceQueue监控对象回收
    public static void demonstrateReferenceQueue() {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        Object obj = new Object();
        WeakReference<Object> weakRef = new WeakReference<>(obj, queue);

        obj = null; // 清除强引用
        System.gc(); // 触发GC

        try {
            // 检查引用队列,如果对象被回收,引用会被加入队列
            Reference<? extends Object> ref = queue.remove(1000);
            if (ref != null) {
                System.out.println("对象已被回收");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2. WeakHashMap的实际应用

import java.util.WeakHashMap;

public class WeakHashMapExample {
    public static void main(String[] args) {
        // WeakHashMap使用弱引用作为键
        WeakHashMap<Key, Value> map = new WeakHashMap<>();

        Key key1 = new Key("key1");
        Key key2 = new Key("key2");

        map.put(key1, new Value("value1"));
        map.put(key2, new Value("value2"));

        System.out.println("Before GC: " + map.size());

        // 清除强引用
        key1 = null;

        System.gc(); // 触发GC

        // key1对应的条目被自动移除
        System.out.println("After GC: " + map.size());
    }

    static class Key {
        private String name;

        public Key(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    static class Value {
        private String value;

        public Value(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return value;
        }
    }
}

3. 弱引用的高级应用

public class WeakReferenceApplications {
    // 1. 观察者模式中的弱引用
    public static class WeakObserver {
        private final List<WeakReference<EventListener>> listeners = new ArrayList<>();

        public void addListener(EventListener listener) {
            listeners.add(new WeakReference<>(listener));
        }

        public void notifyListeners(Event event) {
            Iterator<WeakReference<EventListener>> iterator = listeners.iterator();
            while (iterator.hasNext()) {
                WeakReference<EventListener> ref = iterator.next();
                EventListener listener = ref.get();
                if (listener != null) {
                    listener.onEvent(event);
                } else {
                    // 监听器已被回收,移除引用
                    iterator.remove();
                }
            }
        }
    }

    // 2. 线程本地存储的清理
    public static class WeakThreadLocal<T> {
        private static final Map<Thread, WeakReference<T>> threadMap = new WeakHashMap<>();

        public void set(T value) {
            threadMap.put(Thread.currentThread(), new WeakReference<>(value));
        }

        public T get() {
            WeakReference<T> ref = threadMap.get(Thread.currentThread());
            return ref != null ? ref.get() : null;
        }
    }

    // 3. 对象池的弱引用实现
    public static class WeakObjectPool<T> {
        private final Queue<WeakReference<T>> pool = new LinkedList<>();
        private final Supplier<T> factory;

        public WeakObjectPool(Supplier<T> factory) {
            this.factory = factory;
        }

        public T acquire() {
            while (!pool.isEmpty()) {
                WeakReference<T> ref = pool.poll();
                T obj = ref.get();
                if (obj != null) {
                    return obj;
                }
            }
            return factory.get();
        }

        public void release(T obj) {
            if (obj != null) {
                pool.offer(new WeakReference<>(obj));
            }
        }
    }
}

虚引用(Phantom Reference)

1. 基本特性

虚引用是最弱的引用类型,无法通过虚引用获取对象实例。虚引用的唯一目的是在对象被回收时收到系统通知,通常用于跟踪对象的回收状态。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceExample {
    public static void main(String[] args) {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        Object obj = new Object();

        // 创建虚引用
        PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);

        System.out.println("Phantom reference get: " + phantomRef.get()); // 总是返回null

        // 清除强引用
        obj = null;

        System.gc(); // 触发GC

        try {
            // 虚引用会在对象被回收后加入队列
            Reference<? extends Object> ref = queue.remove(1000);
            if (ref != null) {
                System.out.println("对象已被回收,虚引用进入队列");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

2. 虚引用的实际应用

public class PhantomReferenceApplications {
    // 1. 直接内存管理
    public static class DirectMemoryManager {
        private final ReferenceQueue<ByteBuffer> queue = new ReferenceQueue<>();
        private final Map<PhantomReference<ByteBuffer>, Cleaner> cleaners = new ConcurrentHashMap<>();

        public ByteBuffer allocateDirect(int size) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(size);
            PhantomReference<ByteBuffer> ref = new PhantomReference<>(buffer, queue);

            // 创建清理器
            Cleaner cleaner = Cleaner.create(buffer, () -> {
                System.out.println("清理直接内存: " + size + " bytes");
                // 这里可以执行实际的资源清理
            });

            cleaners.put(ref, cleaner);

            // 启动清理线程
            startCleanupThread();

            return buffer;
        }

        private void startCleanupThread() {
            Thread cleanupThread = new Thread(() -> {
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        Reference<? extends ByteBuffer> ref = queue.remove();
                        if (ref != null) {
                            cleaners.remove(ref);
                            System.out.println("检测到直接内存对象被回收");
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
            cleanupThread.setDaemon(true);
            cleanupThread.start();
        }
    }

    // 2. 资源清理跟踪
    public static class ResourceTracker {
        private static final ReferenceQueue<Object> queue = new ReferenceQueue<>();
        private static final Map<PhantomReference<Object>, String> resources = new ConcurrentHashMap<>();

        static {
            // 启动监控线程
            new Thread(ResourceTracker::monitorResources).start();
        }

        public static void track(Object resource, String description) {
            PhantomReference<Object> ref = new PhantomReference<>(resource, queue);
            resources.put(ref, description);
        }

        private static void monitorResources() {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    PhantomReference<?> ref = (PhantomReference<?>) queue.remove();
                    String description = resources.remove(ref);
                    System.out.println("资源被回收: " + description);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    // 3. 对象生命周期监控
    public static class ObjectLifecycleMonitor {
        private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
        private final Map<PhantomReference<Object>, Long> creationTimes = new ConcurrentHashMap<>();

        public void monitor(Object obj) {
            PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
            creationTimes.put(ref, System.currentTimeMillis());

            startMonitoring();
        }

        private void startMonitoring() {
            Thread monitorThread = new Thread(() -> {
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        PhantomReference<?> ref = (PhantomReference<?>) queue.remove();
                        Long createTime = creationTimes.remove(ref);
                        if (createTime != null) {
                            long lifetime = System.currentTimeMillis() - createTime;
                            System.out.println("对象生命周期: " + lifetime + "ms");
                        }
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
            monitorThread.setDaemon(true);
            monitorThread.start();
        }
    }
}

四种引用对比总结

1. 引用强度对比

public class ReferenceStrengthComparison {
    public static void demonstrateReferenceHierarchy() {
        Object obj = new Object();

        // 强引用:永远不会被回收
        Object strongRef = obj;

        // 软引用:内存不足时才被回收
        SoftReference<Object> softRef = new SoftReference<>(obj);

        // 弱引用:GC运行时就被回收
        WeakReference<Object> weakRef = new WeakReference<>(obj);

        // 虚引用:无法获取对象,仅用于跟踪回收
        PhantomReference<Object> phantomRef = new PhantomReference<>(obj, new ReferenceQueue<>());

        // 引用强度:强引用 > 软引用 > 弱引用 > 虚引用
    }
}

2. 使用场景总结

public class ReferenceUsageSummary {
    /*
     * 强引用 (Strong Reference):
     * - 用途:普通对象引用
     * - 回收时机:永不回收(除非所有强引用都解除)
     * - 使用场景:日常编程,确保对象不被意外回收
     * - 注意:容易造成内存泄漏
     */

    /*
     * 软引用 (Soft Reference):
     * - 用途:缓存实现
     * - 回收时机:内存不足时
     * - 使用场景:图片缓存、数据缓存、连接池
     * - 优点:平衡内存使用和性能
     */

    /*
     * 弱引用 (Weak Reference):
     * - 用途:临时对象引用
     * - 回收时机:下次GC时
     * - 使用场景:WeakHashMap、观察者模式、对象池
     * - 优点:及时回收,避免内存泄漏
     */

    /*
     * 虚引用 (Phantom Reference):
     * - 用途:对象回收跟踪
     * - 回收时机:对象被回收后
     * - 使用场景:直接内存管理、资源清理、生命周期监控
     * - 特点:无法通过虚引用获取对象
     */
}

面试要点总结

  1. 四种引用强度:强引用 > 软引用 > 弱引用 > 虚引用
  2. 回收时机:强引用(永不)、软引用(内存不足)、弱引用(GC时)、虚引用(回收后通知)
  3. 使用场景:强引用(普通对象)、软引用(缓存)、弱引用(临时对象)、虚引用(资源跟踪)
  4. ReferenceQueue:用于监控对象回收状态
  5. 实际应用:缓存实现、WeakHashMap、直接内存管理、对象生命周期监控
  6. 性能考虑:合理使用引用类型可以优化内存使用,避免内存泄漏

关键理解

  • 不同引用类型提供了灵活的内存管理策略
  • 软引用和弱引用是实现高效缓存的重要工具
  • 虚引用用于跟踪对象回收,而不是访问对象
  • 合理使用引用类型可以在性能和内存使用之间找到平衡

编程建议

  • 缓存系统优先考虑软引用
  • 临时对象使用弱引用避免内存泄漏
  • 资源清理可以使用虚引用跟踪
  • 注意强引用可能导致的内存泄漏问题

这四种引用类型是Java内存管理的重要组成部分,对于编写高效的Java程序非常重要。