一、什么是Java内存泄漏
内存泄漏就像你家水龙头没关紧,水一直滴答漏着,虽然每次漏的不多,但时间长了水池就满了。在Java里,对象本该被回收却因为某些引用没释放,导致堆内存逐渐被占满,最终引发OOM(OutOfMemoryError)。
典型场景:
- 静态集合类长期持有对象引用
- 未关闭的数据库连接/文件流
- 监听器未注销
- 线程池未正确shutdown
// 技术栈:Java 8
public class StaticLeak {
static List<byte[]> cache = new ArrayList<>(); // 静态集合生命周期与类相同
void addData() {
while(true) {
cache.add(new byte[1024 * 1024]); // 每次添加1MB数据
// 问题:cache会无限增长,直到OOM
}
}
}
二、如何定位内存泄漏
2.1 工具选择三件套
- jmap:生成堆转储文件
jmap -dump:format=b,file=heap.hprof <pid> - VisualVM:实时监控堆内存曲线
- Eclipse Memory Analyzer (MAT):分析dump文件
2.2 实战分析示例
// 技术栈:Spring Boot 2.7
@RestController
public class LeakController {
private Map<String, Object> sessionMap = new ConcurrentHashMap<>();
@GetMapping("/save")
public String saveSession(@RequestParam String key) {
sessionMap.put(key, new byte[10 * 1024]); // 存入10KB数据
return "success";
}
// 问题:没有删除机制,map会无限膨胀
}
通过MAT分析步骤:
- 查找Retained Size最大的对象
- 查看GC Roots引用链
- 定位到
sessionMap的强引用
三、六种常见泄漏场景修复
3.1 集合类泄漏修复
// 修复方案:使用WeakHashMap
private Map<String, SoftReference<byte[]>> cache = new WeakHashMap<>();
void addData(String key, byte[] data) {
cache.put(key, new SoftReference<>(data));
// 当内存不足时,GC会自动回收
}
3.2 线程池泄漏
错误示范:
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(() -> { /* 长时间任务 */ });
// 忘记调用 pool.shutdown()
正确做法:
try {
pool.shutdown();
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
四、进阶防护策略
4.1 内存限制与监控
在JVM参数中添加:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/oom_dump.hprof
4.2 使用LeakCanary(Android特供)
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9'
}
4.3 代码规范检查
通过SonarQube规则:
- S2696:检查未关闭的资源
- S1215:禁止在finally块中调用return
五、避坑指南
缓存选择:优先用Guava Cache/Caffeine,它们自带LRU淘汰策略
LoadingCache<String, Object> cache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(key -> loadDataFromDB(key));监听器陷阱:
// 注册监听器时 eventBus.register(this); // 必须配套注销 @Override protected void finalize() { eventBus.unregister(this); }
六、总结与最佳实践
- 预防重于治疗:代码审查时重点检查集合操作、资源关闭
- 监控常态化:生产环境配置Prometheus+Granfana监控堆内存
- 工具链固化:将MAT分析流程写入团队Wiki
当遇到OOM时,记住三板斧:
- 用
jstat -gcutil看GC情况 - 用
jmap抓取dump - 用MAT按"Retained Heap"排序分析
评论