首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 內存洩漏深度剖析:別讓「無用卻無法回收」拖垮系統

     編輯:Java 程式設計入門

許多人討論內存洩漏,對 C/C++ 是老生常談,但 Java 開發者也越來越關注。本文小結供參考。

核心概念

  1. C/C++ 由程式員手動管理內存,Java 由 GC 自動回收。
  2. 什麼是內存洩漏? 系統存在無法回收的內存,可能導致 OOM 或崩潰。C/C++ 中分配未釋放即洩漏。
  3. Java 存在內存洩漏:指存在無用但 GC 無法回收的物件。即使存在,也不一定表現出來。必須先承認這點才能深入討論。基本不用太關心,尤其對代碼不講究者更無需關心。
  4. Java 參數均為傳值:基本類型無異議,引用類型亦然。

Java 內存洩漏成因

JVM 回收算法複雜,但核心目標:無引用的物件可回收。要造成洩漏,必須做到:持有對無用物件的引用!

別以為容易:既然無用,怎麼還持有引用?既然持有,怎麼會無用?

經典案例:堆棧內存洩漏

《Effective Java》經典範例:

public class Stack {
    private Object[] elements = new Object[10];
    private int size = 0;

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];  // 錯誤:未清空 elements[size]
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            Object[] oldElements = elements;
            elements = new Object[2 * elements.length + 1];
            System.arraycopy(oldElements, 0, elements, 0, size);
        }
    }
}

原理:壓入 10 個元素後全部彈出,堆棧雖空,但 elements 數組仍持有這 10 個物件引用,導致它們無法被 GC 回收——符合洩漏兩條件:無用、無法回收。

修正:pop() 中應置空:Object result = elements[--size]; elements[size] = null; return result;

洩漏不一定災難

若堆棧使用較少,僅浪費幾 KB,內存達 GB 級影響微乎其微,且很快被回收。但兩種情況會放大後果:

案例 1:靜態集合長期持有

public class Bad {
    public static Stack s = new Stack();
    static {
        s.push(new Object());
        s.pop(); // 這裡發生洩漏
        s.push(new Object()); // 上面物件可回收,等於自愈
    }
}

案例 2:監聽器/回調未移除

註冊監聽器後未在恰當時機移除,導致被監聽物件持有監聽器引用,間接持有大量資源,隨時間累積引發 OOM。

排查與預防

  • 使用 Profiler(如 VisualVM、JProfiler、OptimizeIt)監控堆轉儲分析
  • 集合類用完及時 clear() 或置空引用
  • 靜態集合謹慎使用,必要時用弱引用(WeakHashMap)
  • 監聽器/回調實現移除機制,生命週期結束時調用
  • 內部類持有外部類引用,靜態內部類避免此問題

Java 內存洩漏本質是「邏輯上無用但物理上可達」,理解 GC 根可達性分析算法是徹底解決的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved