一、内存泄漏的那些事儿
内存泄漏就像家里漏水的水龙头,虽然每次只漏一滴,但时间长了能把整个房子淹了。Java应用里,对象本该被回收却赖在堆里不走,最终可能导致OOM(OutOfMemoryError)。举个生活化的例子:你网购了一堆商品,拆完包装后纸箱却堆满客厅不扔——这就是典型的内存泄漏。
技术栈:Java + MAT(Memory Analyzer Tool)
// 示例1:静态集合引起的内存泄漏
public class LeakyShop {
private static List<Order> orderList = new ArrayList<>(); // 静态集合会一直持有对象引用
public void addOrder(Order order) {
orderList.add(order); // 订单对象永远无法被GC回收
}
}
// 正确做法:使用WeakHashMap或定期清理
public class SafeShop {
private static Map<Order, Boolean> weakMap = new WeakHashMap<>();
public void addOrder(Order order) {
weakMap.put(order, true); // 当订单对象无其他引用时会被自动回收
}
}
二、工具界的"侦探三件套"
1. JVM自带工具包
jmap和jstack是JDK自带的瑞士军刀。比如用jmap -histo:live <pid>可以快速查看存活对象分布:
# 生成堆转储文件
jmap -dump:live,format=b,file=heap.hprof 1234
2. VisualVM的立体侦查
图形化工具VisualVM可以实时监控堆内存曲线。当看到老年代(Old Gen)使用率只增不减时,基本可以确认泄漏。
3. MAT的深度解剖
MAT能生成泄漏嫌疑报告。比如分析java.lang.Thread的threadLocals字段,经常是线程池未清理的罪魁祸首。
技术栈演示:MAT分析示例
// 线程局部变量泄漏案例
public class ThreadLeak {
private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
public void doWork() {
threadLocal.set(new byte[1024 * 1024]); // 每个线程持有一个1MB数组
// 忘记调用threadLocal.remove()
}
}
通过MAT的Dominator Tree视图,可以快速定位到这些"巨型"byte数组的持有者。
三、实战中的破案技巧
场景1:Spring容器中的陷阱
@Controller
public class BookingController {
private List<Booking> bookings = new ArrayList<>(); // 实例变量被单例持有
@PostMapping("/book")
public String createBooking(Booking booking) {
bookings.add(booking); // 每次请求都泄漏一个Booking对象
return "success";
}
}
解决方案:
- 改用请求作用域的Bean
- 或使用
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
场景2:缓存忘记设置TTL
// 使用Guava Cache的典型错误
Cache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(1000) // 只设置最大数量
.build(); // 缺少.expireAfterWrite()设置
最佳实践: 必须设置双过期策略(时间和大小)
四、防漏工程指南
- 编码规范:对静态集合、监听器、线程池等高风险点建立Code Review清单
- 监控体系:在Prometheus中配置JVM内存报警规则
- 压测验证:用JMeter模拟长时间运行后观察内存曲线
- 防御性编程:资源使用遵循"谁申请谁释放"原则
// 资源关闭模板示例
public void processFile() {
InputStream is = null;
try {
is = new FileInputStream("data.txt");
// 处理逻辑...
} finally {
if (is != null) {
try { is.close(); }
catch (IOException e) { log.error("关闭流异常", e); }
}
}
}
五、技术选型红黑榜
| 工具/方法 | 优点 | 缺点 |
|---|---|---|
| MAT | 可视化强,支持OQL查询 | 大堆文件分析耗内存 |
| Arthas | 无需重启,动态诊断 | 学习曲线陡峭 |
| JProfiler | 实时监控精准 | 商业收费 |
终极忠告
内存泄漏排查就像破案,要:
- 保留案发现场(保存dump文件)
- 收集所有证据(日志+监控数据)
- 使用交叉验证(至少两种工具确认)
当遇到CMS GC频繁Full GC时,不妨用这个命令组合拳:
jps -lv | grep 应用名
jmap -dump:format=b,file=leak.hprof <pid>
jstack -l <pid> > thread.txt
评论