题目
如何排查和解决由长生命周期对象持有短生命周期对象导致的内存泄漏问题?
信息
- 类型:问答
- 难度:⭐⭐
考点
垃圾回收原理,内存泄漏排查,引用类型理解,GC日志分析
快速回答
核心解决步骤:
- 使用堆转储分析工具(如MAT)定位泄漏对象
- 检查长生命周期容器(如静态Map)对短生命周期对象的强引用
- 将强引用改为弱引用(WeakReference)或软引用(SoftReference)
- 使用
java.lang.ref包中的引用队列配合清理 - 验证GC后内存是否正常回收
问题场景
当长生命周期对象(如静态集合)持有短生命周期对象的强引用时,会导致这些对象无法被GC回收,即使它们已不再使用。例如缓存系统未正确清理过期数据。
原理说明
Java GC通过可达性分析判断对象存活:从GC Roots(如静态变量、活动线程等)出发,无法到达的对象会被回收。强引用会阻止GC回收对象,而弱/软引用则不会。
代码示例
// 错误实现:静态Map强引用导致内存泄漏
public class CacheLeak {
private static Map<String, Object> cache = new HashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, value); // 强引用
}
}
// 正确实现:使用WeakHashMap
public class SafeCache {
private static Map<String, WeakReference<Object>> cache = new WeakHashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, new WeakReference<>(value));
}
}排查步骤
- 启用GC日志:添加JVM参数
-Xlog:gc* -XX:+HeapDumpOnOutOfMemoryError - 分析堆转储:使用MAT工具查看对象支配树,找到被GC Roots引用的无用对象
- 检查引用链:定位持有对象的容器(常见于static集合)
最佳实践
- 对缓存场景使用
WeakHashMap或SoftReference - 定期清理容器:
// 使用引用队列清理失效引用 ReferenceQueue<Object> queue = new ReferenceQueue<>(); WeakReference<Object> ref = new WeakReference<>(object, queue); // 清理线程 while ((ref = (WeakReference) queue.poll()) != null) { cache.remove(ref.getKey()); } - 第三方库:考虑Guava Cache或Caffeine等智能缓存库
常见错误
- 误用
static修饰可变集合 - 在监听器/回调中未及时注销引用
- 混淆软/弱引用:
- 软引用:内存不足时回收(适合缓存)
- 弱引用:下次GC立即回收(适合元数据存储)
扩展知识
- GC Roots类型:静态字段、活动线程栈、JNI引用等
- 引用队列(ReferenceQueue):当引用对象被回收后,引用本身会进入队列便于清理
- 内存泄漏监控:JDK Mission Control或Arthas实时监控堆内存