博客 / 詳情

返回

Java 哪些情況會導致內存泄漏

今天我們來一起聊一聊有哪些情況會導致內存泄漏。

什麼是 內存泄漏 呢?

內存泄漏 是指對象 已經不再被程序使用,但因為某些原因 無法被垃圾回收器回收,長期佔用內存,最終可能引發 OOM(OutOfMemoryError)。

接下來我們看一下常見的幾類內存泄漏場景。

1、生命週期長的集合

將對象放入 靜態 或 生命週期很長 的集合(如 public static List<Object> list = new ArrayList<>();),即使後面不再需要,集合仍持有其引用,導致無法GC

2、未關閉的資源

連接、流等資源未調用 close() 方法關閉。這些資源不僅佔用內存,還可能佔用文件句柄(操作系統分配的唯一標識,憑它,你才能操作文件資源)、網絡連接等系統資源。比如 數據庫連接、文件流(FileInputStream)、Socket連接 等。

public class FileTest {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("test.txt");
            // 讀取文件,未調用 fis.close()
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            // 未調用 fis.close() → fis 持有 Native 引用,無法回收
        }
    }
}

3、ThreadLocal 使用不當

將對象存入 ThreadLocal 後,未在後續調用 remove() 清理。若線程來自線程池(會複用),其 ThreadLocalMap 中的值會一直存活。

public class ThreadLocalTest {
    private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 線程池(核心線程長期存活)
        TThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,
                4,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100),
                new ThreadFactoryBuilder().setNameFormat("my-thread-pool-%d").setDaemon(false).setPriority(Thread.NORM_PRIORITY).build(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        executor.submit(() -> {
            User user = new User("李四", 30);
            userThreadLocal.set(user); // 存儲到 ThreadLocal

            // 業務執行完畢,未調用 remove()
            // 核心線程不會銷燬,ThreadLocal 仍持有 user 引用
        });
    }
}

ps:未進行 remove(),還可能會導致 ThreadLocal 取值串門。

4、內部類與外部類引用

非靜態內部類(或匿名類)會 隱式持有 外部類的引用。如果內部類實例生命週期更長(如被緩存或另一個線程引用),會阻止外部類被回收。

public class OuterClass {
    private byte[] bigData = new byte[1024 * 1024 * 10]; // 10MB 大對象

    // 非靜態內部類
    class InnerClass {
        // 內部類隱式持有 OuterClass 引用
    }

    public InnerClass createInner() {
        return new InnerClass();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        InnerClass inner = outer.createInner();

        // 置空外部類引用,但 inner 仍持有 outer 引用
        outer = null;

        // 若 inner 被靜態變量/線程長期持有 → outer 對象(含 bigData)無法回收
    }
}

5、 監聽器與回調

註冊了 監聽器 回調 後,在對象不再需要時 沒有註銷,導致源對象仍持有監聽器的引用(比如 事件監聽器、消息隊列的消費者等)。

排查工具推薦

  • MAT(Memory Analyzer Tool):分析堆 Dump 文件,定位泄漏對象、引用鏈(誰在持有泄漏對象);
  • VisualVM:JDK 自帶工具,監控內存佔用趨勢,生成堆 Dump,簡單排查泄漏。

這裏我只列了常見的幾種情況,歡迎大家補充其他內存泄漏場景。

人間非淨土,各有千種愁,萬般苦。-- 煙沙九洲

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.