题目
分析并优化内存泄漏场景
信息
- 类型:问答
- 难度:⭐⭐
考点
垃圾回收原理,内存泄漏诊断,GC调优
快速回答
解决内存泄漏问题的关键步骤:
- 使用
jmap生成堆转储文件:jmap -dump:format=b,file=heapdump.hprof <pid> - 通过MAT工具分析对象引用链,定位泄漏源
- 修复静态集合类未清理、未关闭资源等常见问题
- 添加
-XX:+HeapDumpOnOutOfMemoryError参数捕获OOM现场 - 结合
jstat -gcutil监控GC活动
问题场景描述
给定以下存在内存泄漏的代码片段,分析原因并提供优化方案:
public class MemoryLeak {
private static Map<String, Object> cache = new HashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, value);
}
public static void main(String[] args) {
MemoryLeak app = new MemoryLeak();
// 模拟持续添加缓存项
for (int i = 0; i < 1000000; i++) {
app.addToCache("key" + i, new byte[1024]); // 1KB对象
}
}
}原理说明
内存泄漏根本原因:静态Map持续增长且未清除过期条目,导致所有缓存对象始终被GC Roots引用,无法被回收。即使业务不再需要这些数据,仍会占据堆空间。
垃圾回收机制:
- 对象通过可达性分析算法判断是否存活
- GC Roots包括静态变量、活动线程栈变量等
- 本例中
cache作为静态变量属于GC Root
诊断与修复方案
1. 问题定位
使用MAT工具分析堆转储:

发现HashMap$Node数组占据90%+堆空间,通过Merge Shortest Paths to GC Roots可看到被cache静态变量引用。
2. 修复代码
// 方案1:改用WeakHashMap(自动回收无引用键值)
private static Map<String, Object> cache = new WeakHashMap<>();
// 方案2:添加缓存清除逻辑(推荐)
public void removeFromCache(String key) {
cache.remove(key);
}
// 方案3:使用LRU策略的缓存
private static Map<String, Object> cache = new LinkedHashMap<>(100, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES; // 控制最大条目数
}
};最佳实践
- 监控工具组合使用:
jcmd <pid> GC.heap_info查看堆概要jstat -gcutil <pid> 1000每秒监控GC状态
- 避免常见陷阱:
- 静态集合类需设置容量上限
- 及时关闭文件/网络连接等资源(实现
AutoCloseable) - 监听器使用弱引用(
WeakReference)
常见错误
- 误认为
System.gc()能解决内存泄漏(实际可能加重STW停顿) - 未区分内存泄漏(对象无法回收)和内存溢出(空间不足)
- 过度依赖
finalize()方法执行资源清理
扩展知识
- GC调优参数:
-Xmx设置最大堆空间-XX:+UseG1GC启用G1收集器(适合大堆场景)-XX:MaxGCPauseMillis=200设置最大停顿时间目标
- 引用类型:
- 强引用(Strong Reference):默认类型,绝不回收
- 软引用(Soft Reference):内存不足时回收
- 弱引用(Weak Reference):下次GC时回收
- 虚引用(Phantom Reference):用于对象回收跟踪