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 之後呢?
通常會在圖形化工具中分析(例如 jvisualvm 或 Eclipse MAT),因為徒手在二進位檔裡找物件是高難度操作,還有點自虐。
jmap 的其他功能
查看記憶體統計:
jmap -heap <PID>
顯示堆的資訊:大小、使用的 GC、狀態。
類別清單:
jmap -histo <PID>
顯示各類別統計:各型別的物件數與總大小。
輸出範例:
| # | 物件 | 數量 | 位元組 |
|---|---|---|---|
| 1 | |
1200 | 48000 |
| 2 | |
1000 | 32000 |
| 3 | |
200 | 12800 |
這已經能提供一些線索:如果你突然看到某種型別的物件有數百萬個——就該好好查一查了。
2. jvisualvm:視覺化記憶體分析器
jvisualvm 是 JDK 附帶的圖形程式(在 bin 目錄)。它可以連到本機(有時也可遠端)的 Java 程序,進行即時監控、擷取 heap dump、查看記憶體、執行緒、GC、CPU 的統計,甚至做程式的效能剖析。
如果你喜歡用滑鼠點點、看看漂亮的圖表——這就是你的工具。
如何啟動 jvisualvm
在命令列(或 Windows 捷徑)中:
jvisualvm
會開啟視窗,顯示所有本機的 Java 程序清單。
jvisualvm 的主要功能
即時監控記憶體
- 在左側清單選擇程序。
- 切到 Monitor 分頁。
- 你會看到 heap、CPU 使用率、執行緒與類別數量等圖表。
範例:

Heap Dump(記憶體快照)
- 在 Monitor 面板中按下 Heap Dump。
- 幾秒後會出現分析快照的分頁。
- 能看到所有類別、物件數量、與其總大小。
找出「吃記憶體」的物件
- 依大小或數量排序。
- 若發現某個型別(例如 ArrayList 或 String)占用過多記憶體——就該展開調查。
引用分析(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 分析
- 啟動 程式。
- 開啟 jvisualvm 並選擇該程序。
- 在 Monitor 分頁查看記憶體圖表。
- 若一切正常,GC 後記憶體應該會「回落」。
- 若有洩漏,記憶體只會持續上升。
- 擷取 Heap Dump。
- 查看 哪些物件占用最多記憶體。
- 我們這裡會看到 java.util.ArrayList 與 java.lang.String。
- 點擊 物件——看看是誰在引用它。
- 你會發現是靜態欄位 bigList。
如何修正?
- 移除集合中不需要的元素,限制其成長。
- 當大型物件不再需要時,將其引用設為 null。
- 不要把大型集合放在 static,除非它必須常駐。
4. 其他工具:Eclipse MAT、jconsole
Eclipse Memory Analyzer (MAT)
Eclipse MAT 是免費且強大的 heap dump 分析工具。它能顯示哪些物件占用記憶體、哪些物件在「挾持」其他物件——也就是所謂的 retained set。這有助於精準定位洩漏的藏身之處。
它能針對可疑物件產生詳細報告(leak suspects),而且就算是幾 GB 的超大 dump 也能從容處理。
流程很簡單:用 jmap 或 jvisualvm 擷取記憶體快照,在 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、看圖表、點開物件——唯有如此,你才能真正「掌握」應用程式的記憶體,並快速找到問題。
GO TO FULL VERSION