我們首先考察兩種最常見的工具:Task Manager 和 PerfMon。這兩個工具都隨 Windows 一起提供,因此由此起步比較容易。
Task Manager 是一種非常見的 Windows 進程監控程序。您可以通過熟悉的 Ctrl-Alt-Delete 組合鍵來啟動它,或者右擊任務欄。Processes 選項卡顯示了最詳細的信息,如圖 2 所示。
圖 2 中顯示的列已經通過選擇 View --> Select Columns 作了調整。有些列標題非常含糊,但可以在 Task Manager 幫助中找到各列的定義。和進程內存使用情況關系最密切的計數器包括:
- Mem Usage(內存使用):在線幫助將其稱為進程的工作集(盡管很多人稱之為駐留集)——當前在主存中的頁面集。但是這個數值包含能夠和其他進程共享的頁面,因此要注意避免重復計算。比方說,如果要計算共享同一個 DLL 的兩個進程的總內存占用情況,不能簡單地把“內存使用”值相加。
- Peak Mem Usage(內存使用高峰值):進程啟動以來 Mem Usage(內存使用)字段的最大值。
- Page Faults(頁面錯誤):進程啟動以來要訪問的頁面不在主存中的總次數。
- VM Size(虛擬內存大小):聯機幫助將其稱為“分配給進程私有虛擬內存總數。”更確切地說,這是進程所 提交的內存。如果進程保留內存而沒有提交,那么該值就與總地址空間的大小有很大的差別。
雖然 Windows 文檔將 Mem Usage(內存使用)稱為工作集,但在該上下文中,它實際上指的是很多人所說的駐留集(resident set),明白這一點很重要。您可以在 Memory Management Reference 術語表(請參閱 參考資料)中找到這些術語的定義。 工作集 更通常的含義指的是一個邏輯概念,即在某一點上為了避免分頁操作,進程需要駐留在內存中的那些頁面。
隨 Windows 一起提供的另一種 Microsoft 工具是 PerfMon,它監控各種各樣的計數器,從打印隊列到電話。PerfMon 通常在系統路徑中,因此可以在命令行中輸入 perfmon
來啟動它。這個工具的優點是以圖形化的方式顯示計數器,很容易看到計數器隨時間的變化情況。
請在 PerfMon 窗口上方的工具欄中單擊 + 按鈕,這樣會打開一個對話框讓您選擇要監控的計數器,如圖 3a 所示。計數器按照 性能對象分成不同的類別。與內存使用關系最密切的兩個類是 Memory 和 Process。選中計數器然后單擊 Explain 按鈕,就可以看到計數器的定義。說明出現在主對話框下方彈出的單獨的窗口中,如圖 3b 所示。
選擇感興趣的計數器(使用 Ctrl 可以選中多行)和要監控的實例(所分析的應用程序的 Java 進程),然后單擊 Add 按鈕。工具立刻開始顯示選擇的所有計數器的值。您可以選擇用報告、圖表或者直方圖來顯示這些值。圖 4 顯示的是一個直方圖。
右鍵---屬性---圖表---垂直比例(按實際內存數據來填寫,比如TaxiDBXML.exe進程現在使用了600M就可以填寫6000)
如果圖中什么也看不到,表明您可能需要改變比例,右擊圖形區域,選擇 Properties 然后切換到 Graph 選項卡。也可以到計數器的 Data 選項卡改變某個計數器的比例。
要觀察的計數器
不幸的是,PerfMon 使用了與 Task Manager 不同的術語。表 1 列出了最常用的計數器,如果有的話,還給出了相應的 Task Manager 功能:
計數器名 | 類別 | 說明 | 等價的 Task Manager 功能 |
Working Set | Process | 駐留集,當前在實際內存中有多少頁面 | Mem Usage |
Private Bytes | Process | 分配的私有虛擬內存總數,即提交的內存 | VM Size |
Virtual Bytes | Process | 虛擬地址空間的總體大小,包括共享頁面。因為包含保留的內存,可能比前兩個值大很多 | -- |
Page Faults / sec(每秒鍾內的頁面錯誤數) | Process(進程) | 每秒中出現的平均頁面錯誤數 | 鏈接到 Page Faults(頁面錯誤),顯示頁面錯誤總數 |
Committed Bytes(提交的字節數) | Memory(內存) | “提交”狀態的虛擬內存總字節數 | -- |
您可以下載並運行我們用 C 編寫的一個小程序(請參閱 下載部分),來觀察 Task Manager 和 PerfMon 中顯示的這些數量。該程序首先調用 Windows VirtualAlloc
保留內存,然后再提交這些內存,最后使用其中一些內存,每 4,096 個字節寫入一個值,從而將頁面代入工作集。如果運行該例子,並使用 Task Manager 或 PerfMon 觀察,就會發現這些值的變化情況。
![]() ![]() |
![]() |
現在已經看到了應用程序使用多少內存,還需要深入分析內存的實際內容。這一節介紹一些更加復雜的工具,討論什么時候適用輸出結果,以及如何解釋這些結果。
PrcView 是我們要介紹的第一個可以觀察進程地址空間內容的工具(請參閱 參考資料)。該工具不僅能用於觀察內存占用,還可以設置優先級和殺死進程,還有一個很有用的命令行版本,用來列出機器上所有進程的屬性。但我們要介紹的如何使用它觀察內存占用情況。
啟動 PrcView 會看到一個類 Task Manager 的視圖,它顯示了系統中的進程。如果滾動窗口並選中一個 Java 進程,屏幕就會如圖 5 所示。
右擊該 Java 進程打開彈出菜單,或者從上方的菜單條中選擇 Process,就可以看到該進程的一些情況,比如它擁有的線程、加載的 DLL,也可以殺死該進程或者設置其優先級。我們所關心的是考察其內存占用,打開如圖 6 所示的窗口。
現在我們分析一下 PrcView 顯示的地址空間映射的前幾行。第一行表明從地址 0 開始,有一塊長度為 65,536 (64K) 的內存是自由空間。這些地址什么也沒有分配,也不能用於尋址。第二行說明緊跟在后面,從地址 0x00010000 起,有一個長為 8,192 字節(兩個 4K 頁面)的提交內存,即可以尋址並且得到分頁文件中的頁幀支持的內存。然后是一段自由空間,另一段提交空間,如此等等。
碰巧的是,這些地址空間區域對您來說沒有什么意義,因為它是供 Windows 使用的。描述 Windows 地址空間的 Microsoft 文檔指出,這些不同的區域是為兼容 MS-DOS 保留的用戶數據和代碼所用的區域從 4MB 開始(請參閱 參考資料)。
向下滾動窗口,最終會看到某些您能夠清楚識別的地址空間,如圖 7 所示。
圖 7 中高亮顯示的行及其后的一行對應 Java 堆。我們給這里啟動的 Java 進程 1000MB 大小的堆(使用 -mx1000m
),對於該程序而言,這個堆太大了,但這樣在 PrcView 映射中更加清楚。高亮顯示的一行說明堆的提交部分只有 4MB,從地址 0x10180000 開始。緊隨在后面的一行顯示了一個很大的保留區域,這是堆中還沒有提交的那一部分。在啟動過程中,JVM 首先保留出完整的 1000MB 空間(從 0x10180000 到 0x4e980000 范圍之內的地址都不能用),然后提交啟動過程所需要的那一部分,該例中為 4MB。為了驗證該值確實對應當前的堆大小,您可以用 -verbosegc
JVM 選項調用該 Java 程序,可以打印出垃圾收集程序中的詳細信息。從下面的 -verbosegc
輸出中第二個 GC 的第二行可以看出,當前的堆大小大約是 4MB:
>java -mx1000m -verbosegc Hello [ JVMST080: verbosegc is enabled ] [ JVMST082: -verbose:gc output will be written to stderr ] <GC[0]: Expanded System Heap by 65536 bytes <GC(1): GC cycle started Wed Sep 29 16:55:44 2004 <GC(1): freed 417928 bytes, 72% free (3057160/4192768), in 104 ms> <GC(1): mark: 2 ms, sweep: 0 ms, compact: 102 ms> <GC(1): refs: soft 0 (age >= 32), weak 0, final 2, phantom 0> <GC(1): moved 8205 objects, 642080 bytes, reason=4> |
-verbosegc
的輸出格式取決於所用的 JVM 實現,請參閱 參考資料中關於 IBM JVM 的相關文章,或者參考供應商的文檔。
如果活動數據的數量增加,JVM 需要將堆的大小擴展到 4MB 之外,它就會提交稍微多一點的保留區域。就是說,新的區域可以從 0x10580000 開始,與已經提交的堆空間連接在一起。
在 圖 7 所示的 PrcView 窗口中,最下面一行的三個總數給出了進程提交的總內存,這些數據是根據第七列 Type 計算得到的。三個總數為:
- Private:分頁文件支持的提交內存。
- Mapped:直接映射到文件系統的提交內存。
- Image:屬於可執行代碼的提交內存,包括啟動的執行文件和 DLL。
到目前為止,我們只是在根據大小來了解堆在地址空間中的分配情況。為了更好的理解其他一些內存區域,如果能夠觀察內存的內部情形,會對您的了解很有幫助。這就要用到下面將討論的工具 TopToBottom。
TopToBottom 可以從 smidgeonsoft.com 免費獲得(請參閱 參考資料)。該工具沒有任何文檔,但是為當前執行進程提供了一組完備的視圖。您不僅能夠按名稱進程 ID 排序,還能夠按起動時間排序,如果需要了解計算機上程序的啟動順序這一點可能很有用。
圖 8 所示的 TopToBottom 窗口中,進程是按創建時間排序的( View --> Sort --> Creation Time)。
StartUp 選項卡顯示了創建 Java 進程的進程、開始的時間和日期、所用的命令行以及可執行文件和當前目錄的完整路徑。也可以單擊 Environment 選項卡顯示啟動時傳遞給該進程的所有環境變量的值。Modules 選項卡顯示了 Java 進程所用的 DLL,如圖 9 所示。
同樣可以按照不同的方式對列表進行排序。在圖 9 中,它們是按照初始化順序排列的。如果雙擊其中的一行,可以看到 DLL 的詳細信息:其地址和大小、編寫的日期和時間、所依賴的其他 DLL 列表以及加載該 DLL 的所有運行中的進程列表。如果研究這個列表,就會發現有的 DLL 是每個運行的進程都要用到的,比如 NTDLL.DLL;有的在所有 Java 進程間共享,比如 JVM.DLL;而另有一些可能只有一個進程使用。
通過累加各個 DLL 的大小就可以計算出進程所用 DLL 的總大小。但是得到的結果可能會造成誤解,因為它並不意味着進程要消費所有這些內存占用。真正的大小取決於進程實際使用了 DLL 的哪些部分。這些部分將進入進程的工作集。雖然很明顯,但還是要注意 DLL 是只讀的和共享的。如果大量進程都使用一個給定的 DLL,同一時刻只有一組實際內存頁保存 DLL 數據。這些實際的頁面可以映射到不同的地址,進入使用它們的那些進程。Task Manager 之類的工具將工作集看作是共享和非共享頁面的總和,因此很難確定使用 DLL 對內存占用的影響。模塊信息是一種很有用的方式,提供了“最差情況下”由於 DLL 造成的內存占用,需要的話可以使用其他工具作更詳盡地分析。
我們關心的是內存占用情況,請單擊 Memory 選項卡,圖 10 顯示了 Java 程序所用內存的一小部分。
顯示的內容和 PrcView 類似,但是它僅僅顯示了虛擬空間中的提交內存,而沒有保留內存。但是它有兩個優點。首先,它可以更詳盡地描述頁面。比如在圖 10 中專門標記了 Thread 3760 棧區域,而不僅僅是一些讀/寫數據。它是別的其他數據區包括環境、進程參數、進程堆、線程棧和線程環境塊(TEB)。其次,您可以直接在 TopToBottom 中瀏覽甚至搜索內存。您可以搜索文本字符串或者最多 16 字節的十六進制序列。可以將十六進制搜索限制在特定的序列中,在檢索地址引用時這一點很方便。
TopToBottom 也有快照功能,可以把進程的所有信息轉儲到剪貼板中。
VADump 是一種方便的命令行工具,屬於 Microsoft ® Platform SDK 包(請參閱 參考資料)的一部分。它的目的是轉儲特定進程的虛擬地址空間和駐留集。使用 VADump 最簡單的方法就是在命令行中輸入以下命令:
vadump process_id |
process_id 是要分析的進程號。如果不帶參數,則可以顯示 VADump 完整的用法說明。我們建議您將結果通過管道保存到文件中(如 vadump 1234 > output.txt
),因為 VADump 生成的信息非常多,一屏放不下。
輸出中首先給出進程虛擬地址空間的索引:
>vadump -p 3904 Address: 00000000 Size: 00010000 State Free Address: 00010000 Size: 00002000 State Committed Protect Read/Write Type Private Address: 00012000 Size: 0000E000 State Free Address: 00020000 Size: 00001000 State Committed Protect Read/Write Type Private Address: 00021000 Size: 0000F000 State Free Address: 00030000 Size: 00010000 State Committed Protect Read/Write Type Private Address: 00040000 Size: 0003B000 RegionSize: 40000 State Reserved Type Private ................................ |
(為便於閱讀,省略了部分行。)
對於每個塊,都可以看到下列信息:
- Address:十六進制格式,相對於進程虛擬地址空間起始位置的偏移量。
- Size:字節數,用十六進制表示。
- State:自由、保留或提交。
- Protection status:只讀或/讀寫。
- Type:私有(不能被其他進程訪問)、映射(直接來自文件系統)或鏡像(可執行代碼)。
然后列出進程使用的所有 DLL 及其大小,后面是工作集和分頁文件使用的統計信息。
目前為止,所提到的信息都可以從其他工具獲得。但是通過 VADump 的 -o
選項還可以得到更有啟發作用的輸出結果。它可以生成當前工作集的快照(某一給定時刻實際存在於主存中的頁面)。關於該選項文檔沒有提供多少信息,但是在確定駐留集中最重要的部分時,這是一個極其有用的工具,這樣能夠確定最可能的內存優化目標。通過定期記錄內存快照,您還可以使用它確定是否存在內存泄漏。這種模式下,輸出從虛擬地址空間中提交頁面的詳盡轉儲開始,無論這些頁面是否還在主存中:
>vadump -o -p 3904 0x00010000 (0) PRIVATE Base 0x00010000 0x00011000 (0) PRIVATE Base 0x00010000 0x00020000 (0) PRIVATE Base 0x00020000 0x00030000 (0) PRIVATE Base 0x00030000 0x00031000 (0) Private Heap 2 0x00032000 (0) Private Heap 2 0x00033000 (0) Private Heap 2 0x00034000 (0) Private Heap 2 0x00035000 (0) Private Heap 2 0x00036000 (0) Private Heap 2 0x00037000 (0) Private Heap 2 0x00038000 (0) Private Heap 2 0x00039000 (0) Private Heap 2 0x0003A000 (0) Private Heap 2 0x0003B000 (0) Private Heap 2 0x0003C000 (0) Private Heap 2 0x0003D000 (0) Private Heap 2 0x0003E000 (0) Private Heap 2 0x0003F000 (0) Private Heap 2 0x0007C000 (0) Stack for ThreadID 00000F64 0x0007D000 (0) Stack for ThreadID 00000F64 0x0007E000 (0) Stack for ThreadID 00000F64 0x0007F000 (0) Stack for ThreadID 00000F64 0x00080000 (7) UNKNOWN_MAPPED Base 0x00080000 0x00090000 (0) PRIVATE Base 0x00090000 0x00091000 (0) Process Heap 0x00092000 (0) Process Heap 0x00093000 (0) Process Heap ........................... |
滾動到這個長長的列表的最后,您會看到更有趣的信息:目前駐留主存的進程頁面的頁表映射清單:
0xC0000000 > (0x00000000 : 0x003FFFFF) 132 Resident Pages (0x00280000 : 0x00286000) > jsig.dll (0x00290000 : 0x00297000) > xhpi.dll (0x002A0000 : 0x002AF000) > hpi.dll (0x003C0000 : 0x003D8000) > java.dll (0x003E0000 : 0x003F7000) > core.dll (0x00090000 : 0x00190000) > Process Heap segment 0 (0x00190000 : 0x001A0000) > Private Heap 0 segment 0 (0x001A0000 : 0x001B0000) > UNKNOWN Heap 1 segment 0 (0x00380000 : 0x00390000) > Process Heap segment 0 (0x00030000 : 0x00040000) > Private Heap 2 segment 0 (0x00390000 : 0x003A0000) > Private Heap 3 segment 0 (0x00040000 : 0x00080000) > Stack for thread 0 0xC0001000 > (0x00400000 : 0x007FFFFF) 13 Resident Pages (0x00400000 : 0x00409000) > java.exe ................................................................. |
每個映射都對應頁表中的一項,組成了進程工作集另一個 4KB。但要從這些映射中發現應用程序的哪些部分使用了最多的內存仍然很困難,但幸運的是下一部分輸出給出了有用的總結:
Category Total Private Shareable Shared Pages KBytes KBytes KBytes KBytes Page Table Pages 20 80 80 0 0 Other System 10 40 40 0 0 Code/StaticData 1539 6156 3988 1200 968 Heap 732 2928 2928 0 0 Stack 9 36 36 0 0 Teb 5 20 20 0 0 Mapped Data 30 120 0 0 120 Other Data 1314 5256 5252 4 0 Total Modules 1539 6156 3988 1200 968 Total Dynamic Data 2090 8360 8236 4 120 Total System 30 120 120 0 0 Grand Total Working Set 3659 14636 12344 1204 1088 |
最有趣的兩個值通常是 Heap (即 Windows 進程堆)和 Other Data。直接通過調用 Windows API 分配的內存組成了進程堆部分,Other Data 中包括 Java 堆。Grand Total Working Set 對應 Task Manager 的 Mem Usage 和 TEB 字段(進程的線程環境塊所需要的內存,TEB 是一種 Windows 內部結構)。
最后,在 VADump -o
輸出的最下端總結了 DLL、堆和線程棧對工作集的相對貢獻:
Module Working Set Contributions in pages Total Private Shareable Shared Module 9 2 7 0 java.exe 85 5 0 80 ntdll.dll 43 2 0 41 kernel32.dll 15 2 0 13 ADVAPI32.dll 11 2 0 9 RPCRT4.dll 53 6 0 47 MSVCRT.dll 253 31 222 0 jvm.dll 6 3 3 0 jsig.dll 7 4 3 0 xhpi.dll 15 12 3 0 hpi.dll 12 2 0 10 WINMM.dll 21 2 0 19 USER32.dll 14 2 0 12 GDI32.dll 6 2 0 4 LPK.DLL 10 3 0 7 USP10.dll 24 18 6 0 java.dll 22 16 6 0 core.dll 18 14 4 0 zip.dll 915 869 46 0 jitc.dll Heap Working Set Contributions 6 pages from Process Heap (class 0x00000000) 0x00090000 - 0x00190000 6 pages 2 pages from Private Heap 0 (class 0x00001000) 0x00190000 - 0x001A0000 2 pages 0 pages from UNKNOWN Heap 1 (class 0x00008000) 0x001A0000 - 0x001B0000 0 pages 1 pages from Process Heap (class 0x00000000) 0x00380000 - 0x00390000 1 pages 715 pages from Private Heap 2 (class 0x00001000) 0x00030000 - 0x00040000 15 pages 0x008A0000 - 0x009A0000 241 pages 0x04A60000 - 0x04C60000 450 pages 0x054E0000 - 0x058E0000 9 pages 1 pages from Private Heap 3 (class 0x00001000) 0x00390000 - 0x003A0000 1 pages 7 pages from Private Heap 4 (class 0x00001000) 0x051A0000 - 0x051B0000 7 pages Stack Working Set Contributions 4 pages from stack for thread 00000F64 1 pages from stack for thread 00000F68 1 pages from stack for thread 00000F78 1 pages from stack for thread 00000F7C 2 pages from stack for thread 00000EB0 |
通過這種模式還可以用 VADump 獲得兩個或更多 Java 進程的總和內存占用情況(請參閱本文后面的 技巧和竅門)。
更有用的內存分析工具來自 Sysinternals 公司(請參閱 參考資料)。其中一個工具是圖形化的進程管理器,如圖 11 所示,它可以作為 Task Manager 的高級代替品。
Process Explorer 具有和 Task Manager 相同的功能。比方說,您可以得到整個系統性能的動態圖形(通過 View --> System Information...),也可用類似的方式配置主進程視圖中的列。在 Process --> Properties... 中,Process Explorer 提供了進程的更多信息,比如完整路徑和命令行、線程、CPU 實用的動態圖表和私有內存。它的用戶界面非常好,如圖 11 所示。它還可以觀察 DLL 的信息和進程的句柄。您可以使用 Options --> Replace Task Manager 用 Process Explorer 代替默認的 Task Manager。
還可以從 Sysinternals 下載兩個命令行工具:ListDLLs 和 Handle。如果希望在腳本或者程序中集成某種形式的內存監控,這兩個工具非常有用。
ListDLLs 用於觀察 DLL,DLL 可能造成很多內存占用。使用之前請將其添加到路徑中,並使用幫助選項獲得用法說明。您可以用進程 ID 或進程名調用它。下面是我們的 Java 程序調用 DLL 的列表:
>listdlls -r 3904 ListDLLs V2.23 - DLL lister for Win9x/NT Copyright (C) 1997-2000 Mark Russinovich http://www.sysinternals.com --------------------------------------------------------------------- java.exe pid: 3904 Command line: java -mx1000m -verbosegc Hello Base Size Version Path 0x00400000 0x9000 141.2003.0005.0022 C:/WINDOWS/system32/java.exe 0x77f50000 0xa7000 5.01.2600.1217 C:/WINDOWS/System32/ntdll.dll 0x77e60000 0xe6000 5.01.2600.1106 C:/WINDOWS/system32/kernel32.dll 0x77dd0000 0x8d000 5.01.2600.1106 C:/WINDOWS/system32/ADVAPI32.dll 0x78000000 0x87000 5.01.2600.1361 C:/WINDOWS/system32/RPCRT4.dll 0x77c10000 0x53000 7.00.2600.1106 C:/WINDOWS/system32/MSVCRT.dll 0x10000000 0x178000 141.2004.0003.0001 C:/Java141/jre/bin/jvm.dll ### Relocated from base of 0x10000000: 0x00280000 0x6000 141.2004.0003.0001 C:/Java141/jre/bin/jsig.dll ### Relocated from base of 0x10000000: 0x00290000 0x7000 141.2004.0003.0001 C:/Java141/jre/bin/xhpi.dll ### Relocated from base of 0x10000000: 0x002a0000 0xf000 141.2004.0003.0001 C:/Java141/jre/bin/hpi.dll 0x76b40000 0x2c000 5.01.2600.1106 C:/WINDOWS/system32/WINMM.dll 0x77d40000 0x8c000 5.01.2600.1255 C:/WINDOWS/system32/USER32.dll 0x7e090000 0x41000 5.01.2600.1346 C:/WINDOWS/system32/GDI32.dll 0x629c0000 0x8000 5.01.2600.1126 C:/WINDOWS/system32/LPK.DLL 0x72fa0000 0x5a000 1.409.2600.1106 C:/WINDOWS/system32/USP10.dll ### Relocated from base of 0x10000000: 0x003c0000 0x18000 141.2004.0003.0001 C:/Java141/jre/bin/java.dll ### Relocated from base of 0x10000000: 0x003e0000 0x17000 141.2004.0003.0001 C:/Java141/jre/bin/core.dll ### Relocated from base of 0x10000000: 0x04a40000 0x12000 141.2004.0003.0001 C:/Java141/jre/bin/zip.dll ### Relocated from base of 0x10000000: 0x04df0000 0x3a1000 141.2004.0003.0001 C:/Java141/jre/bin/jitc.dll |
也可以使用 listdlls -r java
命令,列出所有運行的 Java 進程及其使用的 DLL。
Handle 給出進程所用句柄(文件、套接字等)的列表。解壓 Handle 下載文件,並將其添加到路徑中,然后試着運行它。對於我們的 Java 程序,輸出結果如下所示:
>handle -p 3904 Handle v2.2 Copyright (C) 1997-2004 Mark Russinovich Sysinternals - www.sysinternals.com ------------------------------------------------------------------ java.exe pid: 3904 99VXW67/cem c: File C:/wsappdev51/workspace/Scratch 4c: File C:/wsappdev51/workspace/Scratch/verbosegc.out 50: File C:/wsappdev51/workspace/Scratch/verbosegc.out 728: File C:/WebSphere MQ/Java/lib/com.ibm.mq.jar 72c: File C:/WebSphere MQ/Java/lib/fscontext.jar 730: File C:/WebSphere MQ/Java/lib/connector.jar 734: File C:/WebSphere MQ/Java/lib/jms.jar 738: File C:/WebSphere MQ/Java/lib/jndi.jar 73c: File C:/WebSphere MQ/Java/lib/jta.jar 740: File C:/WebSphere MQ/Java/lib/ldap.jar 744: File C:/WebSphere MQ/Java/lib/com.ibm.mqjms.jar 748: File C:/WebSphere MQ/Java/lib/providerutil.jar 74c: File C:/Java141/jre/lib/ext/oldcertpath.jar 750: File C:/Java141/jre/lib/ext/ldapsec.jar 754: File C:/Java141/jre/lib/ext/JawBridge.jar 758: File C:/Java141/jre/lib/ext/jaccess.jar 75c: File C:/Java141/jre/lib/ext/indicim.jar 760: File C:/Java141/jre/lib/ext/ibmjceprovider.jar 764: File C:/Java141/jre/lib/ext/ibmjcefips.jar 768: File C:/Java141/jre/lib/ext/gskikm.jar 794: File C:/Java141/jre/lib/charsets.jar 798: File C:/Java141/jre/lib/xml.jar 79c: File C:/Java141/jre/lib/server.jar 7a0: File C:/Java141/jre/lib/ibmjssefips.jar 7a4: File C:/Java141/jre/lib/security.jar 7a8: File C:/Java141/jre/lib/graphics.jar 7ac: File C:/Java141/jre/lib/core.jar |
可以看出我們的進程中有一個句柄,該句柄指向類路徑目錄和幾個 JAR 文件。事實上,該進程還有更多的句柄,但默認情況下該工具僅顯示引用文件的句柄。使用 -a
參數就可以顯示其他句柄:
>handle -a -p 3904 Handle v2.2 Copyright (C) 1997-2004 Mark Russinovich Sysinternals - www.sysinternals.com ------------------------------------------------------------------ java.exe pid: 3904 99VXW67/cem c: File C:/wsappdev51/workspace/Scratch 4c: File C:/wsappdev51/workspace/Scratch/verbosegc.out 50: File C:/wsappdev51/workspace/Scratch/verbosegc.out 71c: Semaphore 720: Thread java.exe(3904): 3760 724: Event 728: File C:/WebSphere MQ/Java/lib/com.ibm.mq.jar 72c: File C:/WebSphere MQ/Java/lib/fscontext.jar 730: File C:/WebSphere MQ/Java/lib/connector.jar 734: File C:/WebSphere MQ/Java/lib/jms.jar 738: File C:/WebSphere MQ/Java/lib/jndi.jar 73c: File C:/WebSphere MQ/Java/lib/jta.jar 740: File C:/WebSphere MQ/Java/lib/ldap.jar 744: File C:/WebSphere MQ/Java/lib/com.ibm.mqjms.jar 748: File C:/WebSphere MQ/Java/lib/providerutil.jar 74c: File C:/Java141/jre/lib/ext/oldcertpath.jar 750: File C:/Java141/jre/lib/ext/ldapsec.jar 754: File C:/Java141/jre/lib/ext/JawBridge.jar 758: File C:/Java141/jre/lib/ext/jaccess.jar 75c: File C:/Java141/jre/lib/ext/indicim.jar 760: File C:/Java141/jre/lib/ext/ibmjceprovider.jar 764: File C:/Java141/jre/lib/ext/ibmjcefips.jar 768: File C:/Java141/jre/lib/ext/gskikm.jar 76c: Key HKCU 770: Semaphore 774: Thread java.exe(3904): 3964 778: Event 77c: Semaphore 780: Semaphore 784: Thread java.exe(3904): 3960 788: Event 78c: Thread java.exe(3904): 3944 790: Event 794: File C:/Java141/jre/lib/charsets.jar 798: File C:/Java141/jre/lib/xml.jar 79c: File C:/Java141/jre/lib/server.jar 7a0: File C:/Java141/jre/lib/ibmjssefips.jar 7a4: File C:/Java141/jre/lib/security.jar 7a8: File C:/Java141/jre/lib/graphics.jar 7ac: File C:/Java141/jre/lib/core.jar 7b0: Event 7b4: Thread java.exe(3904): 3940 7b8: Event 7bc: Semaphore 7c0: Directory /BaseNamedObjects 7c4: Key HKLM/SOFTWARE/Windows NT/Drivers32 7c8: Semaphore 7cc: Semaphore 7d0: Event 7d4: Desktop /Default 7d8: WindowStation /Windows/WindowStations/WinSta0 7dc: Event 7e0: WindowStation /Windows/WindowStations/WinSta0 7e4: Event 7e8: Section 7ec: Port 7f0: Directory /Windows 7f4: Key HKLM 7f8: Directory /KnownDll 7fc: KeyedEvent /KernelObjects/CritSecOutOfMemoryEvent |
如果關心內存的使用,句柄是一個重要因素,因為每個句柄都要消耗一些空間。具體的數量取決於操作系統版本和句柄的類型。一般而言,句柄不應該對內存占用產生很大影響。只要數一數該工具輸出的行數,就可以判定句柄是不是太多,或者是否還在增長。無論出現哪種情況,都值得注意,建議進行更細致的分析。
![]() ![]() |
![]() |
現在您已經操作(不是雙關語,handle 還有一個含義是句柄)了我們要介紹的所有工具,下面是您單獨或一起使用這些工具,改進內存監控的一些方法。
為了找到應用程序的進程 ID,以便在 VADump 這樣的命令行工具中使用,請在 Task Manager 中打開 Applications 選項卡右擊所關心的進程。選擇 Go To Process,這樣就會在 Processes 選項卡中看到對應的 ID。
是否對那些都命名為 Java 或 javaw 的進程感到困惑,希望找出您要分析的那個進程?如果從 IDE 或腳本中啟動 Java 進程,要確定使用了哪一個 JVM 和發送給 Java 進程的命令行參數可能很困難。這些信息可以在 TopToBottom Startup 選項卡中找到。您可以看到調用 JVM 使用的完整命令行和進程啟動的時間。
是否遇到過保存文件卻得到提示說文件正被另一個進程使用的情況?或者嘗試關閉您認為可靠的程序而得到錯誤消息的情況?您可以使用 SysInternals Process Explorer 工具的 Handle Search 功能發現誰在搗亂。只要打開 Search 對話框並輸入文件名即可。ProcExp 將遍歷所有打開的句柄,並確定相應的進程。最終常常會發現,關閉用戶界面后,編輯器或者 Web 瀏覽器還留下一個小的存根進程在運行。
您可以使用 VADump 的 -o
選項獲得進程當前工作集的詳細視圖,以及有多少是共享的。獲得一個 Java 程序在系統上運行的內存轉儲,然后再啟動另一個並轉儲。只要比較每個結果的 Code/StaticData 部分,就會發現“Shareable”字節變成了“Shared”,從而稍微降低了內存占用的增加。
Windows 實現了一種“清除”進程駐留集的策略,在其看起來不再有用的時候予以清除。為了說明這一點,打開 Task Manager 的 Processes 選項框,便可以看到要監控的應用程序進程,然后最小化應用程序窗口,看看 Mem Usage 字段發生了什么變化!
對於 Windows Server 2003 和 Windows NT,Microsoft 提供了一個有趣的稱為 ClearMem 的工具,如果希望進一步研究 Windows 下應用程序使用內存的情況,它可能非常有用(請參閱 參考資料)。該工具確定了實際內存的大小,分配足夠的內存,很快地占用分配的內存然后將其釋放。這樣就增加了其他應用程序的內存占用壓力,反復運行 ClearMem 的結果是迫使應用程序占用的內存數量減少到最小。
![]() ![]() |
![]() |
本文簡要介紹了 Windows 如何管理內存,考察了一些最有用的免費工具,您可以用這些工具監控 Java 應用程序的內存使用。無疑您還會發現和使用其他的工具,無論從 Web 上免費下載產品還是購買商業產品,我們都希望澄清相互矛盾的術語會對您有所幫助。通常要確定您測量的目標的惟一方法就是做試驗,比如我們用於示范 Task Manager 的 VM Size(虛擬內存大小)和 Mem Usage(內存使用)含義的 C 程序。
當然這些工具只能幫助確定問題的所在,如何解決還要靠您自己。多數時候您會發現 Java 堆獲取了內存的一大部分,您需要深入分析代碼,確定對象引用是否超出了必要的時間。這方面有更多的工具和文章可以提供幫助, 參考資料部分給出了一些有用的鏈接,可以為您指出正確的方向。