CodeGym /課程 /JAVA 25 SELF /記憶體分析工具:jmap、jvisualvm

記憶體分析工具:jmap、jvisualvm

JAVA 25 SELF
等級 64 , 課堂 3
開放

1. jmap:命令列魔法

自動垃圾回收當然很棒。不過,即便是 Java 應用也可能會「漏」(OutOfMemoryError)、變慢,或因為記憶體使用不當而出現意料之外的卡頓。原因可能很多:

  • 記憶體洩漏(memory leaks):應該被移除的物件還在「存活」。
  • 集合中數據量過大。
  • 快取邏輯有誤。
  • 資源未釋放(例如執行緒、檔案)。

你的任務——學會快速定位這些問題。否則你可能會從程式設計師變成「bug 開發者」!

什麼是 jmap?

jmap 是 JDK 內建的工具。它能取得正在執行的 Java 程序的記憶體「快照」(heap dump)。之後即可用其他工具開啟此快照,查看哪些物件占用記憶體、有多少個、以及誰在引用它們。

Heap dump 就像是你的堆的照片:其中有哪些物件、它們的型別、關聯與大小。

如何使用 jmap

先找到程序的 PID

首先需要知道你的 Java 應用程式所處程序的識別碼(PID)。可用多種方法:

透過 jps(JDK 的另一個工具):

jps -l

你會看到 Java 程序清單及其 PID。

也可用工作管理員(Task Manager)或在 Linux 使用 ps

擷取記憶體 dump

jmap -dump:format=b,file=heap.bin <PID>
  • format=b——二進位格式(適合分析)。
  • file=heap.bin——輸出檔名。
  • <PID>——程序識別碼。

範例:

jmap -dump:format=b,file=heap.bin 12345

拿到 heap dump 之後呢?
通常會在圖形化工具中分析(例如 jvisualvmEclipse MAT),因為徒手在二進位檔裡找物件是高難度操作,還有點自虐。

jmap 的其他功能

查看記憶體統計:

jmap -heap <PID>

顯示堆的資訊:大小、使用的 GC、狀態。

類別清單:

jmap -histo <PID>

顯示各類別統計:各型別的物件數與總大小。

輸出範例:

# 物件 數量 位元組
1
[C
1200 48000
2
java.lang.String
1000 32000
3
java.util.HashMap
200 12800

這已經能提供一些線索:如果你突然看到某種型別的物件有數百萬個——就該好好查一查了。

2. jvisualvm:視覺化記憶體分析器

jvisualvm 是 JDK 附帶的圖形程式(在 bin 目錄)。它可以連到本機(有時也可遠端)的 Java 程序,進行即時監控、擷取 heap dump、查看記憶體、執行緒、GC、CPU 的統計,甚至做程式的效能剖析。

如果你喜歡用滑鼠點點、看看漂亮的圖表——這就是你的工具。

如何啟動 jvisualvm

在命令列(或 Windows 捷徑)中:

jvisualvm

會開啟視窗,顯示所有本機的 Java 程序清單。

jvisualvm 的主要功能

即時監控記憶體

  • 在左側清單選擇程序。
  • 切到 Monitor 分頁。
  • 你會看到 heap、CPU 使用率、執行緒與類別數量等圖表。

範例:

jvisualvm-monitor

Heap Dump(記憶體快照)

  • Monitor 面板中按下 Heap Dump
  • 幾秒後會出現分析快照的分頁。
  • 能看到所有類別、物件數量、與其總大小。

找出「吃記憶體」的物件

  • 依大小或數量排序。
  • 若發現某個型別(例如 ArrayListString)占用過多記憶體——就該展開調查。

引用分析(Reference Graph)

  • 點擊某個類別——即可看到有哪些對該類別物件的引用。
  • 可以一路追到根源:為什麼物件沒有被垃圾回收器釋放。

執行緒分析

  • Threads 分頁——顯示所有執行緒與其狀態。
  • 可以找出「卡住」或過度活躍的執行緒。

效能剖析

  • Profiler 分頁——可量測哪些方法最耗時間或記憶體。
  • 這屬於較深度的分析,但找記憶體洩漏通常前幾步就夠了。

3. 實作:分析記憶體洩漏

有洩漏的範例程式

import java.util.ArrayList;
import java.util.List;

public class MemoryLeakDemo {
    // 靜態集合——經典範例!
    private static final List<String> bigList = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1_000_000; i++) {
            bigList.add("字串編號 " + i);
            if (i % 100_000 == 0) {
                System.out.println("已新增: " + i);
                Thread.sleep(500); // 留點時間用來分析
            }
        }
        System.out.println("完成!請不要關閉應用程式,請打開 jvisualvm。");
        Thread.sleep(600_000); // 10 分鐘——供分析用的時間
    }
}

發生了什麼事?

  • 我們建立了靜態清單,並往裡面加入一百萬個字串。
  • 加入完成後,程式會「停住」(等待),讓你可以連上去用工具分析。

用 jvisualvm 分析

  1. 啟動 程式。
  2. 開啟 jvisualvm 並選擇該程序。
  3. Monitor 分頁查看記憶體圖表。
    • 若一切正常,GC 後記憶體應該會「回落」。
    • 若有洩漏,記憶體只會持續上升。
  4. 擷取 Heap Dump
  5. 查看 哪些物件占用最多記憶體。
    • 我們這裡會看到 java.util.ArrayListjava.lang.String
  6. 點擊 物件——看看是誰在引用它。
    • 你會發現是靜態欄位 bigList

如何修正?

  • 移除集合中不需要的元素,限制其成長。
  • 當大型物件不再需要時,將其引用設為 null。
  • 不要把大型集合放在 static,除非它必須常駐。

4. 其他工具:Eclipse MAT、jconsole

Eclipse Memory Analyzer (MAT)

Eclipse MAT 是免費且強大的 heap dump 分析工具。它能顯示哪些物件占用記憶體、哪些物件在「挾持」其他物件——也就是所謂的 retained set。這有助於精準定位洩漏的藏身之處。

它能針對可疑物件產生詳細報告(leak suspects),而且就算是幾 GB 的超大 dump 也能從容處理。

流程很簡單:用 jmapjvisualvm 擷取記憶體快照,在 MAT 中開啟,按下 Leak Suspects Report——就能拿到現成報告,裡面已標出潛在問題點。

jconsole

jconsole 是一個簡單好用的 JVM 即時監控工具。它會顯示目前使用了多少記憶體、CPU 負載如何、有多少執行緒、以及垃圾回收的狀態。

與較進階的 VisualVM 不同,jconsole 不分析 heap dump,但非常適合快速診斷——當你需要一眼看懂應用程式此刻在做什麼時。

5. 分析記憶體的常見錯誤

錯誤 №1:擷取了不是目標程序的 dump。 一台機器上常同時跑著多個 Java 應用,很容易搞錯 PID。請用 jps 再三確認,確實是在分析你的程式。

錯誤 №2:在沒有洩漏的地方期待看到「洩漏」。 GC 不一定會立刻刪除物件——有時只有在 Full GC 後記憶體才會「放手」。別因一次回收後記憶體沒有下降就慌張——請看長期趨勢。

錯誤 №3:忽略了靜態/快取物件的影響。 很多洩漏都與物件被存在 static 欄位或全域集合中有關。請檢查誰在引用那些「吃記憶體」的物件——往往是 static

錯誤 №4:沒有在動態中進行剖析。 Heap dump 很有用,但有時候更重要的是觀察記憶體隨時間的變化(在 jvisualvm 中看圖表)。如果記憶體持續上升——就該展開調查。

錯誤 №5:忘了解除事件監聽器/處理器。 如果你註冊了事件監聽,卻沒有解除——就算不再使用,該物件仍會一直「活」在記憶體中。

建議: 別害怕動手玩工具!擷取 dump、看圖表、點開物件——唯有如此,你才能真正「掌握」應用程式的記憶體,並快速找到問題。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION