一、什么是内存泄漏?

内存泄漏就像你租了个仓库却忘记退租,每个月白白交租金。在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(); 
    }
});

修复方案

  1. 使用ThreadPoolExecutor并设置allowCoreThreadTimeOut
  2. 提交任务时添加超时控制:
Future<?> future = pool.submit(task);
future.get(30, TimeUnit.SECONDS);  // 超时取消任务

五、预防内存泄漏的编码习惯

  1. 对静态集合使用WeakHashMap或定期清理
  2. 实现AutoCloseable接口管理资源:
try (Connection conn = DriverManager.getConnection(url)) {
    // 自动调用conn.close()
}
  1. 避免在匿名内部类中隐式持有外部类引用(如Handler导致Activity泄漏)

六、高级技巧:JVM参数调优

通过调整GC策略缓解问题(治标不治本):

-XX:+UseG1GC -Xmx4g -XX:+HeapDumpOnOutOfMemoryError

但记住:工具再好不如代码写得牢