面试官问:“mysql的索引有哪些?”

36 阅读15分钟

开场白(展现系统理解)

"Java中有四种引用类型:强引用、软引用、弱引用、虚引用,它们的主要区别在于对象的可达性和GC回收时机不同。这四种引用从强到弱,配合不同的使用场景,可以实现更灵活的内存管理。我从实际应用场景来说明。"


一、四种引用类型对比

1.1 概览表格

引用类型回收时机使用场景是否进入ReferenceQueue
强引用(Strong)永不回收普通对象引用❌ 否
软引用(SoftReference)内存不足时缓存✅ 是
弱引用(WeakReference)下次GC时缓存、ThreadLocal✅ 是
虚引用(PhantomReference)不影响回收堆外内存清理✅ 是(必须)

1.2 强度对比

强引用 > 软引用 > 弱引用 > 虚引用
  ↓        ↓        ↓        ↓
永不回收  内存不足  下次GC   任何时候

二、强引用(Strong Reference)

2.1 基本概念

定义: 最常见的引用类型,只要强引用存在,GC永远不会回收被引用的对象。

// 所有通过new创建的对象都是强引用
Object obj = new Object();
String str = "Hello";
List<Integer> list = new ArrayList<>();

// 只要obj变量存在,这个Object对象就不会被回收

2.2 内存泄漏风险

// ❌ 问题示例:集合持有强引用导致内存泄漏
public class CacheManager {
    
    // 静态Map,永远不释放
    private static Map<String, byte[]> cache = new HashMap<>();
    
    public void addCache(String key, byte[] data) {
        cache.put(key, data);  // 强引用,永不释放
    }
    
    public byte[] getCache(String key) {
        return cache.get(key);
    }
}

// 问题:
// 1. cache持有所有对象的强引用
// 2. 即使对象不再使用,也无法被GC回收
// 3. 最终导致OOM

2.3 正确的资源管理

// ✅ 解决方案1:及时清空引用
public void process() {
    List<BigData> dataList = loadData();  // 大量数据
    
    // 处理数据
    for (BigData data : dataList) {
        handle(data);
    }
    
    // 处理完后,显式清空引用
    dataList.clear();
    dataList = null;  // 帮助GC
}

// ✅ 解决方案2:使用局部变量(自动释放)
public void process() {
    {
        List<BigData> dataList = loadData();
        handle(dataList);
    }  // 离开作用域,dataList自动释放
    
    // 后续代码...
}

// ✅ 解决方案3:使用软引用或弱引用(见后文)

三、软引用(SoftReference)

3.1 基本概念

定义: 内存足够时保留,内存不足时回收。适合做缓存。

回收时机:

  • JVM内存充足:保留对象
  • JVM内存不足:回收对象,避免OOM

3.2 基本使用

import java.lang.ref.SoftReference;

public class SoftReferenceDemo {
    
    public static void main(String[] args) {
        // 1. 创建一个对象
        byte[] data = new byte[10 * 1024 * 1024];  // 10MB
        
        // 2. 创建软引用
        SoftReference<byte[]> softRef = new SoftReference<>(data);
        
        // 3. 清除强引用
        data = null;
        
        // 4. 通过软引用获取对象
        byte[] cachedData = softRef.get();
        if (cachedData != null) {
            System.out.println("缓存命中:" + cachedData.length);
        } else {
            System.out.println("缓存已被回收");
        }
    }
}

3.3 实战案例:图片缓存

/**
 * 图片缓存管理器(使用软引用)
 * 场景:浏览大量图片,内存足够时缓存,内存不足时自动释放
 */
public class ImageCacheManager {
    
    // 使用软引用缓存图片
    private Map<String, SoftReference<BufferedImage>> imageCache = 
        new ConcurrentHashMap<>();
    
    /**
     * 获取图片
     * @param url 图片URL
     * @return 图片对象
     */
    public BufferedImage getImage(String url) {
        // 1. 尝试从缓存获取
        SoftReference<BufferedImage> softRef = imageCache.get(url);
        if (softRef != null) {
            BufferedImage image = softRef.get();
            if (image != null) {
                System.out.println("缓存命中:" + url);
                return image;  // 缓存命中
            }
        }
        
        // 2. 缓存未命中,重新加载
        System.out.println("缓存未命中,加载图片:" + url);
        BufferedImage image = loadImageFromDisk(url);
        
        // 3. 放入缓存(软引用)
        imageCache.put(url, new SoftReference<>(image));
        
        return image;
    }
    
    /**
     * 从磁盘加载图片
     */
    private BufferedImage loadImageFromDisk(String url) {
        try {
            return ImageIO.read(new File(url));
        } catch (IOException e) {
            throw new RuntimeException("加载图片失败", e);
        }
    }
    
    /**
     * 清空缓存
     */
    public void clearCache() {
        imageCache.clear();
    }
}

使用示例:

public class ImageViewer {
    
    private ImageCacheManager cacheManager = new ImageCacheManager();
    
    public void viewImages(List<String> imageUrls) {
        for (String url : imageUrls) {
            // 获取图片(自动缓存管理)
            BufferedImage image = cacheManager.getImage(url);
            display(image);
        }
    }
}

// 优点:
// 1. 内存充足时:图片缓存在内存,快速访问
// 2. 内存不足时:自动释放缓存,避免OOM
// 3. 无需手动管理:GC自动决定何时回收

3.4 配合ReferenceQueue使用

/**
 * 高级用法:监控软引用的回收
 */
public class AdvancedSoftReferenceCache {
    
    private Map<String, SoftReference<Object>> cache = new ConcurrentHashMap<>();
    private ReferenceQueue<Object> queue = new ReferenceQueue<>();
    
    public AdvancedSoftReferenceCache() {
        // 启动线程,监控被回收的引用
        startCleanupThread();
    }
    
    /**
     * 添加缓存
     */
    public void put(String key, Object value) {
        // 创建软引用,关联ReferenceQueue
        SoftReference<Object> softRef = new SoftReference<>(value, queue);
        cache.put(key, softRef);
    }
    
    /**
     * 获取缓存
     */
    public Object get(String key) {
        SoftReference<Object> softRef = cache.get(key);
        return softRef != null ? softRef.get() : null;
    }
    
    /**
     * 监控线程:清理已被回收的缓存项
     */
    private void startCleanupThread() {
        Thread cleanupThread = new Thread(() -> {
            while (true) {
                try {
                    // 阻塞等待被回收的引用
                    Reference<?> ref = queue.remove();
                    
                    // 从缓存中移除对应的key
                    cache.entrySet().removeIf(entry -> 
                        entry.getValue() == ref
                    );
                    
                    System.out.println("清理了一个被回收的缓存项");
                    
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        
        cleanupThread.setDaemon(true);
        cleanupThread.start();
    }
}

四、弱引用(WeakReference)

4.1 基本概念

定义: 比软引用更弱,下次GC时一定会被回收,无论内存是否充足。

回收时机:

  • 只要发生GC,弱引用的对象就会被回收

4.2 基本使用

import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
    
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建对象
        Object obj = new Object();
        
        // 2. 创建弱引用
        WeakReference<Object> weakRef = new WeakReference<>(obj);
        
        // 3. 清除强引用
        obj = null;
        
        System.out.println("GC前:" + weakRef.get());  // 输出:java.lang.Object@xxx
        
        // 4. 触发GC
        System.gc();
        Thread.sleep(100);
        
        System.out.println("GC后:" + weakRef.get());  // 输出:null
    }
}

4.3 实战案例:WeakHashMap

/**
 * WeakHashMap:key是弱引用
 * 场景:监听器注册、临时数据存储
 */
public class WeakHashMapDemo {
    
    public static void main(String[] args) throws InterruptedException {
        WeakHashMap<User, String> map = new WeakHashMap<>();
        
        // 1. 创建key对象(有强引用)
        User user1 = new User("张三");
        User user2 = new User("李四");
        
        // 2. 放入Map
        map.put(user1, "数据1");
        map.put(user2, "数据2");
        
        System.out.println("初始大小:" + map.size());  // 2
        
        // 3. 清除user1的强引用
        user1 = null;
        
        // 4. 触发GC
        System.gc();
        Thread.sleep(100);
        
        System.out.println("GC后大小:" + map.size());  // 1
        // user1被回收,对应的Entry也被自动移除
    }
}

class User {
    private String name;
    
    public User(String name) {
        this.name = name;
    }
    
    @Override
    protected void finalize() {
        System.out.println(name + " 被回收");
    }
}

应用场景:

场景1:监听器管理

/**
 * 使用WeakHashMap管理监听器,避免内存泄漏
 */
public class EventManager {
    
    // key是监听器对象(弱引用),value是监听信息
    private WeakHashMap<EventListener, String> listeners = new WeakHashMap<>();
    
    /**
     * 注册监听器
     */
    public void register(EventListener listener) {
        listeners.put(listener, "registered");
    }
    
    /**
     * 触发事件
     */
    public void fireEvent(Event event) {
        for (EventListener listener : listeners.keySet()) {
            listener.onEvent(event);
        }
    }
    
    // 优点:
    // 1. 监听器对象被释放后,自动从Map中移除
    // 2. 无需手动unregister,避免内存泄漏
}

场景2:ThreadLocal的实现

/**
 * ThreadLocal内部使用弱引用
 */
public class ThreadLocalDemo {
    
    // ThreadLocal的key是弱引用
    private static ThreadLocal<User> userContext = new ThreadLocal<>();
    
    public static void main(String[] args) {
        // 设置线程本地变量
        userContext.set(new User("张三"));
        
        // 使用
        User user = userContext.get();
        System.out.println(user.getName());
        
        // 清理(重要!)
        userContext.remove();
    }
}

// ThreadLocal内部实现(简化版):
class ThreadLocal<T> {
    
    static class ThreadLocalMap {
        // Entry的key是ThreadLocal的弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}

// 为什么使用弱引用?
// 1. ThreadLocal对象不再使用时,可以被GC回收
// 2. 避免ThreadLocal对象泄漏
// 3. 但value还需要手动remove()

4.4 弱引用缓存

/**
 * 使用弱引用实现自动清理的缓存
 */
public class WeakCache<K, V> {
    
    private Map<K, WeakReference<V>> cache = new ConcurrentHashMap<>();
    
    /**
     * 添加缓存
     */
    public void put(K key, V value) {
        cache.put(key, new WeakReference<>(value));
    }
    
    /**
     * 获取缓存
     */
    public V get(K key) {
        WeakReference<V> ref = cache.get(key);
        if (ref != null) {
            V value = ref.get();
            if (value == null) {
                // 对象已被回收,移除key
                cache.remove(key);
            }
            return value;
        }
        return null;
    }
    
    /**
     * 清理已被回收的缓存项
     */
    public void cleanup() {
        cache.entrySet().removeIf(entry -> 
            entry.getValue().get() == null
        );
    }
}

五、虚引用(PhantomReference)

5.1 基本概念

定义: 最弱的引用,无法通过虚引用获取对象(get()永远返回null)。

作用: 跟踪对象被GC回收的状态,用于资源清理。

特点:

  • get() 永远返回 null
  • 必须配合 ReferenceQueue 使用
  • 对象回收前会被放入队列

5.2 基本使用

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

public class PhantomReferenceDemo {
    
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建引用队列
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        
        // 2. 创建对象
        Object obj = new Object();
        
        // 3. 创建虚引用(必须传入队列)
        PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
        
        // 4. 无法通过虚引用获取对象
        System.out.println("通过虚引用获取:" + phantomRef.get());  // 输出:null
        
        // 5. 清除强引用
        obj = null;
        
        // 6. 触发GC
        System.gc();
        Thread.sleep(100);
        
        // 7. 检查队列(对象被回收后会进入队列)
        if (queue.poll() != null) {
            System.out.println("对象已被回收,执行清理工作");
        }
    }
}

5.3 实战案例:堆外内存管理

/**
 * 使用虚引用管理堆外内存(DirectByteBuffer的实现原理)
 */
public class DirectMemoryManager {
    
    // 引用队列
    private static ReferenceQueue<DirectBuffer> queue = new ReferenceQueue<>();
    
    // 保存虚引用的集合(防止虚引用本身被回收)
    private static Set<PhantomReference<DirectBuffer>> phantomRefs = 
        ConcurrentHashMap.newKeySet();
    
    static {
        // 启动清理线程
        startCleanupThread();
    }
    
    /**
     * 分配堆外内存
     */
    public static DirectBuffer allocate(int capacity) {
        // 1. 创建DirectBuffer对象
        DirectBuffer buffer = new DirectBuffer(capacity);
        
        // 2. 创建虚引用
        PhantomReference<DirectBuffer> ref = 
            new PhantomReference<>(buffer, queue);
        
        // 3. 保存虚引用(防止被回收)
        phantomRefs.add(ref);
        
        return buffer;
    }
    
    /**
     * 清理线程:监控对象回收,清理堆外内存
     */
    private static void startCleanupThread() {
        Thread cleanupThread = new Thread(() -> {
            while (true) {
                try {
                    // 阻塞等待被回收的引用
                    PhantomReference<DirectBuffer> ref = 
                        (PhantomReference<DirectBuffer>) queue.remove();
                    
                    // 执行清理工作
                    System.out.println("DirectBuffer被回收,清理堆外内存");
                    
                    // 移除虚引用
                    phantomRefs.remove(ref);
                    
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        
        cleanupThread.setDaemon(true);
        cleanupThread.setName("DirectMemory-Cleaner");
        cleanupThread.start();
    }
}

/**
 * DirectBuffer示例类
 */
class DirectBuffer {
    private long address;  // 堆外内存地址
    private int capacity;
    
    public DirectBuffer(int capacity) {
        this.capacity = capacity;
        // 分配堆外内存(native方法)
        this.address = allocateMemory(capacity);
        System.out.println("分配堆外内存:" + capacity + " bytes");
    }
    
    private native long allocateMemory(int capacity);
    
    @Override
    protected void finalize() throws Throwable {
        // finalize不保证执行,所以使用虚引用更可靠
        System.out.println("finalize被调用");
    }
}

为什么DirectByteBuffer使用虚引用?

// DirectByteBuffer的实现原理(简化版)
public class DirectByteBuffer {
    
    private long address;  // 堆外内存地址
    
    public DirectByteBuffer(int capacity) {
        // 1. 分配堆外内存
        address = unsafe.allocateMemory(capacity);
        
        // 2. 创建Cleaner(继承自PhantomReference)
        Cleaner.create(this, new Deallocator(address));
    }
    
    // 清理器
    private static class Deallocator implements Runnable {
        private long address;
        
        Deallocator(long address) {
            this.address = address;
        }
        
        @Override
        public void run() {
            // 释放堆外内存
            unsafe.freeMemory(address);
            System.out.println("堆外内存已释放");
        }
    }
}

// 好处:
// 1. DirectByteBuffer对象被GC回收后
// 2. 虚引用会进入队列
// 3. Cleaner线程执行Deallocator.run()
// 4. 释放堆外内存,避免内存泄漏

5.4 虚引用 vs finalize

方式优点缺点
finalize()简单1. 不保证执行
2. 影响GC性能
3. 对象复活风险
虚引用1. 可靠
2. 不影响GC
稍复杂
// ❌ 不推荐:使用finalize
class Resource {
    @Override
    protected void finalize() throws Throwable {
        // 问题:
        // 1. finalize可能不执行
        // 2. 对象可能复活(this赋值给其他变量)
        // 3. 影响GC性能
        cleanup();
    }
}

// ✅ 推荐:使用虚引用
class Resource {
    private static ReferenceQueue<Resource> queue = new ReferenceQueue<>();
    
    public Resource() {
        new PhantomReference<>(this, queue);
    }
    
    // 清理线程监控队列,执行清理
}

六、引用类型的选择

6.1 选择决策树

需要缓存数据?
├─ 是 → 可以被GC回收吗?
│   ├─ 内存不足时回收 → 软引用(SoftReference)
│   │   └─ 场景:图片缓存、大对象缓存
│   │
│   └─ 下次GC就回收 → 弱引用(WeakReference)
│       └─ 场景:临时缓存、WeakHashMap
│
└─ 否 → 需要跟踪对象回收?
    ├─ 是 → 虚引用(PhantomReference)
    │   └─ 场景:堆外内存管理、资源清理
    │
    └─ 否 → 强引用(Strong Reference)
        └─ 场景:普通对象引用

6.2 应用场景总结

引用类型典型场景代码示例
强引用普通对象Object obj = new Object()
软引用内存敏感缓存图片缓存、大数据缓存
弱引用自动清理缓存WeakHashMap、ThreadLocal
虚引用资源清理DirectByteBuffer、文件句柄

6.3 实战组合使用

/**
 * 多级缓存:强引用 + 软引用 + 磁盘
 */
public class MultiLevelCache {
    
    // 一级缓存:强引用(LRU,容量有限)
    private LRUCache<String, Object> level1Cache = new LRUCache<>(100);
    
    // 二级缓存:软引用(容量较大)
    private Map<String, SoftReference<Object>> level2Cache = new ConcurrentHashMap<>();
    
    /**
     * 获取数据
     */
    public Object get(String key) {
        // 1. 尝试从一级缓存获取(最快)
        Object value = level1Cache.get(key);
        if (value != null) {
            return value;
        }
        
        // 2. 尝试从二级缓存获取
        SoftReference<Object> softRef = level2Cache.get(key);
        if (softRef != null) {
            value = softRef.get();
            if (value != null) {
                // 放回一级缓存
                level1Cache.put(key, value);
                return value;
            }
        }
        
        // 3. 从磁盘加载
        value = loadFromDisk(key);
        if (value != null) {
            level1Cache.put(key, value);
            level2Cache.put(key, new SoftReference<>(value));
        }
        
        return value;
    }
    
    private Object loadFromDisk(String key) {
        // 从磁盘加载数据
        return null;
    }
}

七、面试高频追问

Q1: "软引用和弱引用的区别?"

标准答案:

"主要区别是回收时机:软引用在内存不足时才回收,弱引用在下次GC时一定回收。软引用适合做缓存,因为可以尽量保留数据;弱引用适合做临时映射,比如WeakHashMap、ThreadLocal的key。"

详细对比:

// 软引用:内存充足时保留
SoftReference<byte[]> softRef = new SoftReference<>(new byte[1024]);
// 只有在内存不足时,JVM才会回收

// 弱引用:下次GC就回收
WeakReference<byte[]> weakRef = new WeakReference<>(new byte[1024]);
// 只要发生GC,就会被回收

Q2: "ThreadLocal为什么使用弱引用?"

标准答案:

"ThreadLocal的key使用弱引用,是为了避免ThreadLocal对象泄漏。当ThreadLocal对象不再使用时,可以被GC回收,对应的Entry的key会变成null。但是value还是强引用,所以需要手动调用remove()清理。"

详细解释:

// ThreadLocal内部结构
class ThreadLocal<T> {
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;  // ❌ value是强引用
            
            Entry(ThreadLocal<?> k, Object v) {
                super(k);  // ✅ key是弱引用
                value = v;
            }
        }
    }
}

// 为什么使用弱引用?
ThreadLocal<User> userContext = new ThreadLocal<>();
userContext.set(new User("张三"));

// 如果key是强引用:
// userContext = null后,ThreadLocal对象无法回收(被Entry的key引用)

// 使用弱引用后:
// userContext = null后,ThreadLocal对象可以被GC回收
// Entry的key变成null,但value还在!需要手动remove()

userContext.remove();  // ✅ 清理value

Q3: "虚引用有什么用?"

标准答案:

"虚引用主要用于跟踪对象的回收状态,用来做资源清理。最典型的应用是DirectByteBuffer,它使用虚引用(Cleaner)来管理堆外内存。当DirectByteBuffer对象被GC回收时,Cleaner会自动释放对应的堆外内存。"

Q4: "如何避免软引用和弱引用的内存泄漏?"

标准答案:

"虽然软引用和弱引用会被GC回收,但引用对象本身不会自动清理。需要配合ReferenceQueue使用,监控被回收的引用,及时从Map中移除对应的key。"

// ❌ 问题:引用对象不会自动清理
Map<String, SoftReference<Object>> cache = new HashMap<>();
cache.put("key1", new SoftReference<>(obj));
// obj被回收后,SoftReference对象还在Map中

// ✅ 解决方案:使用ReferenceQueue
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Map<String, SoftReference<Object>> cache = new HashMap<>();

SoftReference<Object> ref = new SoftReference<>(obj, queue);
cache.put("key1", ref);

// 监控线程
while (true) {
    Reference<?> ref = queue.remove();
    cache.values().remove(ref);  // 清理
}

Q5: "什么时候使用软引用,什么时候使用弱引用?"

标准答案:

"看对缓存的容忍度:数据可以保留就用软引用,必须及时清理就用弱引用。比如图片缓存用软引用(希望尽量保留),监听器映射用弱引用(不需要一直保留)。"

决策依据:

// 使用软引用:希望缓存尽量保留
// 场景:图片缓存、大数据缓存、计算结果缓存
SoftReference<BufferedImage> imageCache;
SoftReference<List<Data>> dataCache;

// 使用弱引用:缓存可以及时清理
// 场景:临时映射、自动清理的关联
WeakHashMap<Object, Metadata> metadata;
WeakReference<Session> sessionRef;

八、实战案例

案例1:OOM排查 - 软引用缓存失效

问题背景: 线上服务OOM,原因是图片缓存占用内存过大。

问题代码:

// ❌ 问题:使用强引用缓存
public class ImageCache {
    
    // 强引用,永不释放
    private Map<String, BufferedImage> cache = new ConcurrentHashMap<>();
    
    public BufferedImage getImage(String url) {
        return cache.computeIfAbsent(url, this::loadImage);
    }
}

// 问题:
// 1. 缓存无限增长
// 2. 图片对象占用大量内存
// 3. 最终OOM

解决方案:

// ✅ 方案1:改用软引用
public class ImageCache {
    
    private Map<String, SoftReference<BufferedImage>> cache = 
        new ConcurrentHashMap<>();
    
    public BufferedImage getImage(String url) {
        SoftReference<BufferedImage> ref = cache.get(url);
        BufferedImage image = ref != null ? ref.get() : null;
        
        if (image == null) {
            image = loadImage(url);
            cache.put(url, new SoftReference<>(image));
        }
        
        return image;
    }
}

// ✅ 方案2:使用Guava Cache(更推荐)
public class ImageCache {
    
    private LoadingCache<String, BufferedImage> cache = 
        CacheBuilder.newBuilder()
            .maximumSize(1000)               // 最大容量
            .expireAfterAccess(10, TimeUnit.MINUTES)  // 10分钟不访问过期
            .softValues()                    // 使用软引用
            .build(new CacheLoader<String, BufferedImage>() {
                @Override
                public BufferedImage load(String url) {
                    return loadImage(url);
                }
            });
    
    public BufferedImage getImage(String url) {
        return cache.getUnchecked(url);
    }
}

案例2:ThreadLocal内存泄漏

问题背景: 使用ThreadLocal后,内存持续增长,最终OOM。

问题代码:

// ❌ 问题:ThreadLocal未清理
public class UserContextHolder {
    
    private static ThreadLocal<User> userContext = new ThreadLocal<>();
    
    public static void setUser(User user) {
        userContext.set(user);
        // ❌ 问题:没有remove()
    }
    
    public static User getUser() {
        return userContext.get();
    }
}

// 在线程池中使用
@Service
public class OrderService {
    
    public void createOrder(Order order) {
        // 设置用户上下文
        UserContextHolder.setUser(getCurrentUser());
        
        // 业务逻辑
        processOrder(order);
        
        // ❌ 问题:线程归还线程池后,ThreadLocal没清理
        // 下次使用这个线程,还能获取到旧的User对象
    }
}

解决方案:

// ✅ 解决方案1:手动清理
public void createOrder(Order order) {
    try {
        UserContextHolder.setUser(getCurrentUser());
        processOrder(order);
    } finally {
        UserContextHolder.clear();  // 清理ThreadLocal
    }
}

// ✅ 解决方案2:使用TransmittableThreadLocal(阿里开源)
private static TransmittableThreadLocal<User> userContext = 
    new TransmittableThreadLocal<>();

// ✅ 解决方案3:在拦截器统一处理
@Component
public class UserContextInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(...) {
        UserContextHolder.setUser(getCurrentUser());
        return true;
    }
    
    @Override
    public void afterCompletion(...) {
        UserContextHolder.clear();  // 统一清理
    }
}

九、面试回答模板

结构化回答(5步法)

第一步:概括分类(20秒)

"Java有四种引用类型:强引用、软引用、弱引用、虚引用,它们的区别主要是GC回收时机不同。"

第二步:重点说明常用的(40秒)

"日常开发中,强引用最常见,就是普通的对象引用。软引用适合做缓存,内存不足时才回收;弱引用在下次GC时就回收,适合做临时映射,比如WeakHashMap。"

第三步:实战场景(1分钟)

"我们项目中用软引用做过图片缓存,内存充足时保留图片,内存不足时自动释放,避免OOM。ThreadLocal内部的key也是弱引用,这样ThreadLocal对象不用时可以被GC回收。"

第四步:注意事项(30秒)

"使用软引用和弱引用要注意,引用对象本身不会自动清理,需要配合ReferenceQueue监控回收,及时清理。ThreadLocal使用完一定要调用remove()。"

第五步:加分项(可选)

"虚引用主要用于资源清理,比如DirectByteBuffer用Cleaner(虚引用的子类)管理堆外内存,比finalize更可靠。"


十、总结

关键要点

  1. 强引用:永不回收,最常用
  2. 软引用:内存不足回收,适合缓存
  3. 弱引用:下次GC回收,适合临时映射
  4. 虚引用:跟踪回收,适合资源清理

使用建议

DO(应该做的)

  • 缓存大对象时使用软引用
  • 使用ThreadLocal后及时remove()
  • 配合ReferenceQueue监控回收
  • 优先使用成熟框架(Guava Cache)

DON'T(不应该做的)

  • 不要滥用软引用/弱引用
  • 不要忘记清理ThreadLocal
  • 不要用finalize,用虚引用
  • 不要在生产环境过度依赖GC

十一、参考资料


祝你面试顺利!记住:理解原理 + 实战场景 = 满分回答。