JVM 內存泄漏分析
初始代碼
public class App {
private static final Map<Long, Object> productCache = new HashMap<>();
private static final Random RANDOM = new Random();
public static void main(String[] args) {
try {
while (true) {
long id = System.nanoTime();
productCache.put(id, new Object());
if (productCache.size() % 1_000_000 == 0) {
System.out.println("The current number of cached objects: " + productCache.size());
}
}
} catch (OutOfMemoryError e) {
System.err.println("\n OOM!!! The final number of cached objects: " + productCache.size());
e.printStackTrace();
}
}
}
編譯運行
命令行在APP.java所在目錄執行
# 編譯
javac App.java
# 運行,最大堆內存大小為 256MB,自動生成快照
java -Xms128m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/tmp/demo1-heap.hprof App
分析內存快照
使用MAT1.7.0版本,MAT入門參考
修復代碼
改用帶淘汰機制的緩存替換 HashMap 為 LinkedHashMap 並實現 LRU(最近最少使用)淘汰策略,限制緩存最大容量:
public class App {
// private static final Map<Long, Object> productCache = new HashMap<>();
private static final Random RANDOM = new Random();
private static final Map<Long, Object> productCache = new LinkedHashMap<Long, Object>(1024, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Long, Object> eldest) {
// More than one million goods will be eliminated if they have not been used for the longest time
return size() > 1_000_000;
}
};
public static void main(String[] args) {
try {
while (true) {
long id = System.nanoTime();
productCache.put(id, new Object());
if (productCache.size() % 1_000_000 == 0) {
System.out.println("The current number of cached objects: " + productCache.size());
}
}
} catch (OutOfMemoryError e) {
System.err.println("\n OOM!!! The final number of cached objects: " + productCache.size());
e.printStackTrace();
}
}
}
再次編譯運行發現緩存維持在1_000_000
常見內存泄漏場景及解決
根據分析結果,針對性修復:
靜態集合未清理
問題:static List/Map 等容器長期持有對象引用,未及時移除(如緩存未設置過期策略)。
解決:改用弱引用集合(WeakHashMap)、設置緩存淘汰機制(如 LRU),或定期清理無效數據。
資源未關閉
問題:數據庫連接、文件流、網絡連接等資源未關閉,導致對象無法回收。
解決:使用 try-with-resources 自動關閉資源,或在 finally 塊中顯式釋放。
監聽器 / 回調未移除
問題:註冊的監聽器(如 GUI 事件、觀察者模式)未註銷,導致被監聽對象長期引用。
解決:在對象銷燬前移除監聽器,或使用弱引用實現回調。
線程泄漏
問題:線程池核心線程持有大對象引用,或未正確停止的線程(如 Thread 未中斷)。
解決:控制線程池核心線程數,使用 ThreadLocal 時注意清理(remove()),確保線程能正常終止。
類加載器泄漏
問題:自定義類加載器加載的類未被回收(如熱部署場景),導致元空間溢出。
解決:避免長期持有類加載器引用,確保加載的類及其依賴可被 GC 回收。