并发编程中的内存泄漏更隐蔽、更致命!ThreadLocal、线程池、监听器...处处是坑!
一、ThreadLocal导致的内存泄漏⚠️
问题代码
public class UserContext {
private static ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
// ❌ 忘记清理!
}
// 在线程池中使用
executor.execute(() -> {
UserContext.setUser(new User("张三"));
doSomething();
// 任务结束,但ThreadLocal没清理
// 线程被复用,ThreadLocal中的User对象一直存在!
});
内存泄漏原理
Thread对象
↓ 强引用
ThreadLocalMap (threadLocals字段)
↓ Entry (弱引用key)
ThreadLocal对象 → null(被GC)
↓ 强引用value
User对象 → 无法GC!(泄漏!)
正确做法✅
public class UserContext {
private static ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
// ✅ 清理方法
public static void clear() {
userHolder.remove();
}
}
// 使用
executor.execute(() -> {
try {
UserContext.setUser(new User("张三"));
doSomething();
} finally {
UserContext.clear(); // 一定要清理!
}
});
二、线程池未关闭
问题代码
public class Service {
public void process() {
// ❌ 每次创建新线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(() -> {
// 任务
});
// 没有shutdown,线程池一直存在!
}
}
正确做法✅
public class Service {
// ✅ 单例线程池
private static final ExecutorService executor =
Executors.newFixedThreadPool(10);
public void process() {
executor.execute(() -> {
// 任务
});
}
// ✅ 应用关闭时
public void shutdown() {
executor.shutdown();
}
}
三、监听器未移除
问题代码
public class EventBus {
private List<EventListener> listeners = new CopyOnWriteArrayList<>();
public void register(EventListener listener) {
listeners.add(listener);
}
// ❌ 没有unregister方法
}
// 使用
EventListener listener = new HeavyListener(); // 大对象
eventBus.register(listener);
listener = null; // 想要GC
// 但listeners还持有引用,无法GC!
正确做法✅
public class EventBus {
private List<EventListener> listeners = new CopyOnWriteArrayList<>();
public void register(EventListener listener) {
listeners.add(listener);
}
// ✅ 提供移除方法
public void unregister(EventListener listener) {
listeners.remove(listener);
}
}
// 或使用弱引用
public class EventBus {
private List<WeakReference<EventListener>> listeners =
new CopyOnWriteArrayList<>();
public void register(EventListener listener) {
listeners.add(new WeakReference<>(listener));
}
}
四、静态集合持有对象
问题代码
public class Cache {
// ❌ 静态集合,永远不会GC
private static Map<String, byte[]> cache = new HashMap<>();
public static void put(String key, byte[] data) {
cache.put(key, data); // 数据越来越多
}
}
正确做法✅
// 方案1:使用弱引用
private static Map<String, WeakReference<byte[]>> cache =
new WeakHashMap<>();
// 方案2:设置容量上限
private static Map<String, byte[]> cache =
new LinkedHashMap<String, byte[]>(100, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 100; // 超过100个移除最老的
}
};
// 方案3:使用Guava Cache(自动过期)
private static LoadingCache<String, byte[]> cache =
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(loader);
五、内部类持有外部类引用
问题代码
public class Outer {
private byte[] data = new byte[1024 * 1024]; // 1MB
public void start() {
// ❌ 非静态内部类持有Outer引用
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
// 长时间运行
// Outer对象无法GC
}
}
}).start();
}
}
正确做法✅
public class Outer {
private byte[] data = new byte[1024 * 1024];
public void start() {
// ✅ 静态内部类或lambda
new Thread(() -> {
while (true) {
// 不持有Outer引用
}
}).start();
}
}
六、检测工具
1. MAT(Memory Analyzer Tool)
# 1. 生成heap dump
jmap -dump:live,format=b,file=heap.bin <pid>
# 2. 用MAT分析
# 查看Dominator Tree
# 查找泄漏对象
2. VisualVM
# 启动VisualVM
jvisualvm
# 连接应用
# 查看堆内存
# 生成heap dump
3. JProfiler
// 分析内存分配
// 查看对象引用链
// 实时监控内存
七、预防内存泄漏checklist✅
□ ThreadLocal用完调用remove()
□ 线程池应用关闭时shutdown()
□ 监听器提供unregister方法
□ 静态集合设置容量上限或过期时间
□ 避免非静态内部类持有外部引用
□ 定期检查堆内存
□ 使用WeakReference/SoftReference
□ 及时关闭资源(finally块)
八、面试高频问答💯
Q: ThreadLocal为什么会内存泄漏?
A: ThreadLocalMap的Entry的key是弱引用,value是强引用。key被GC后,value无法被访问但仍存在。
Q: 如何避免ThreadLocal泄漏?
A: 用完后调用remove()。
Q: 为什么静态集合容易泄漏?
A: 静态变量生命周期等同于应用,集合中的对象永远不会GC。
Q: 如何检测内存泄漏?
A:
- 生产:监控堆内存增长趋势
- 排查:MAT分析heap dump
- 预防:代码Review + 压测
下一篇→ 手写限流器:令牌桶、漏桶、滑动窗口🚰