一、什么是内存泄漏?
内存泄漏就像你租了个仓库却忘记退租,每个月白白交租金。在Java中,对象被分配了内存空间,但因为某些原因无法被垃圾回收器(GC)回收,久而久之内存被占满,程序就会像堵车的马路一样越来越慢,最终OOM(OutOfMemoryError)崩溃。
举个典型例子:
public class LeakyClass {
private static List<byte[]> leakyList = new ArrayList<>();
public void loadData() {
while (true) {
// 每次循环添加1MB数据但从未清理
leakyList.add(new byte[1024 * 1024]);
}
}
}
这段代码中,leakyList作为静态集合持续增长,即使方法执行结束,这些byte数组也无法被回收。
二、内存泄漏的常见场景
1. 静态集合滥用
比如用static Map缓存数据却忘记设置过期策略:
public class UserCache {
private static Map<Integer, User> cache = new HashMap<>();
public static void addUser(User user) {
cache.put(user.getId(), user); // 用户数据永远留在内存
}
}
2. 未关闭的资源
数据库连接、文件流等:
public void readFile() {
try {
FileInputStream fis = new FileInputStream("data.txt");
// 忘记调用 fis.close()
} catch (IOException e) {
e.printStackTrace();
}
}
3. 监听器未注销
GUI或事件监听中常见:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 如果组件未移除,监听器会一直持有引用
}
});
三、定位内存泄漏的工具
1. JDK自带神器:VisualVM
连接应用后查看堆内存曲线,如果持续上升且Full GC后不下降,基本实锤泄漏。
2. MAT(Memory Analyzer Tool)
分析堆转储文件(heap dump)的核武器:
jmap -dump:format=b,file=heap.hprof <pid> # 生成dump文件
在MAT中查看Dominator Tree,找到占用内存最大的对象链。
3. JProfiler的堆遍历
商业工具但直观高效,能直接显示对象引用关系图:
// 示例:查找某个类的所有实例
JProfiler.getSnapshot().getClasses(myClass).getInstances();
四、实战修复案例
场景:线程池中的任务泄漏
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(() -> {
while (true) {
// 任务未正常终止条件
processData();
}
});
修复方案:
- 使用
ThreadPoolExecutor并设置allowCoreThreadTimeOut - 提交任务时添加超时控制:
Future<?> future = pool.submit(task);
future.get(30, TimeUnit.SECONDS); // 超时取消任务
五、预防内存泄漏的编码习惯
- 对静态集合使用
WeakHashMap或定期清理 - 实现
AutoCloseable接口管理资源:
try (Connection conn = DriverManager.getConnection(url)) {
// 自动调用conn.close()
}
- 避免在匿名内部类中隐式持有外部类引用(如Handler导致Activity泄漏)
六、高级技巧:JVM参数调优
通过调整GC策略缓解问题(治标不治本):
-XX:+UseG1GC -Xmx4g -XX:+HeapDumpOnOutOfMemoryError
但记住:工具再好不如代码写得牢!
评论