今天我們來一起聊一聊有哪些情況會導致內存泄漏。
什麼是 內存泄漏 呢?
內存泄漏 是指對象 已經不再被程序使用,但因為某些原因 無法被垃圾回收器回收,長期佔用內存,最終可能引發 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,簡單排查泄漏。
這裏我只列了常見的幾種情況,歡迎大家補充其他內存泄漏場景。
人間非淨土,各有千種愁,萬般苦。-- 煙沙九洲