程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 如何監控Java應用程序的Windows內存使用情況

如何監控Java應用程序的Windows內存使用情況

編輯:關於JAVA

Java 技術最知名的一個優點是:與其他語言如 C 程序員不同,Java 程序員不需要對令 人畏懼的內存分配和釋放負責。Java 運行庫可以為您管理這些任務。每個實例化的對象都自 動在堆中分配內存,垃圾收集程序定期收回不再使用的對象所占據的內存。但是您還不能完 全撒手不管。您仍然需要監控程序的內存使用情況,因為 Java 進程的內存不僅僅包括堆中 分配的對象。它還包括程序的字節碼(JVM 在運行時解釋執行的指令)、JIT 代碼(已經為 目標處理器編譯過的代碼)、任何本機代碼和 JVM 使用的一些元數據(異常表、行號表等等 )。情況更為復雜的是,某些類型的內存(如本機庫)可以在進程間共享,因此確定 Java 應用程序的內存占用可能是一項非常艱巨的任務。

有大量在 Windows 監控內存使用的工具,但不幸的是沒有一種能夠提供您需要的所有信 息。更糟的是,這些形形色色的工具甚至沒有一個公共的詞匯表。但本文會助您一臂之力, 文中將介紹一些最有用的、可免費獲得的工具,並提供了如何使用它們的技巧。

Windows 內存:一次旋風般的旅行

了解本文要討論的工具之前,需要對 Windows 如何管理內存有基本的理解。Windows 使 用一種 分頁請求虛擬內存系統,現在我們就來分析一下這種系統。

虛擬地址空間

虛擬內存的概念在上個世紀五十年代就提出了,當時是作為解決不能一次裝入實際內存的 程序這一復雜問題的方案提出的。在虛擬內存系統中,程序可以訪問超出可用物理內存的更 大的地址集合,專用內存管理程序將這些邏輯地址映射到實際地址,使用磁盤上的臨時存儲 保存超出的部分。

Windows 所使用的現代虛擬內存實現中,虛擬存儲被組織成大小相同的單位,稱為 頁。 每個操作系統進程占用自己的 虛擬地址空間,即一組可以讀寫的虛擬內存頁。每個頁可以有 三種狀態:

自由:還沒有進程使用這部分地址空間。如果企圖訪問這部分空間,無論讀寫都會造成某 種運行時失效。該操作將導致彈出一個 Windows 對話框,提示出現了訪問沖突。(Java 程 序不會造成這種錯誤,只有用支持指針的語言編寫的程序才可能造成這種問題。)

保留:這部分地址空間保留給進程,以供將來使用,但是在交付之前,不能訪問該地址空 間。很多 Java 堆在一開始處於保留狀態。

提交:程序可以訪問的內存,得到了完全 支持,就是說已經在分頁文件中分配了頁幀。 提交的頁只有在第一次被引用時才裝入主存,因此成為 請求式分頁。

圖 1 說明了進程地址空間中的虛擬頁如何映射到內存中的物理頁幀。

圖 1. 進程地址空間中的虛擬頁到物理頁幀的映射

如果運行的是 32 位機器(如一般的 Intel 處理器),那麼進程的整個虛擬地址空間就 是 4GB,因為這是用 32 位所能尋址的最大地址空間。Windows 通常不會允許您訪問地址空 間中的所有這些內存,進程自己使用的只有不到一半,其他供 Windows 使用。這 2 GB 的私 有空間部分包含了 JVM 執行程序所需要的多數內存:Java 堆、JVM 本身的 C 堆、用於程序 線程的棧、保存字節碼和即時編譯方法的內存、本機方法所分配的內存等等。後面介紹地址 空間映射時,我們將描述這些不同的部分。

希望分配了大量連續內存區域但這些內存不馬上同時使用的程序常常結合使用保留內存和 提交內存。JVM 以這種方式分配 Java 堆。參數 -mx 告訴 JVM 堆有多大,但 JVM 通常不在 一開始就分配所有這些內存。它 保留 -mx 所規定的大小,標記能夠提交的整個地址范圍。 然後它僅僅提交一部分內存,這也是內存管理程序需要在實際內存和分頁文件中分配頁來支 持它們的那一部分。以後活動數據數量增加,堆需要擴展,JVM 可以再提交多一點內存,這 些內存與當前提交的部分相鄰。通過這種方式,JVM 可以維護單一的、連續的堆空間,並根 據需要增長(關於如何使用 JVM 堆參數請參閱 參考資料)。

實際內存

物理存儲頁組織成大小相同的單位,通常稱為 頁幀。操作系統有一種數據結構稱為 頁表 ,將應用程序訪問的虛擬頁映射到主存中的實際頁幀。沒有裝入的頁保存在磁盤上的臨時分 頁文件中。當應用程序要訪問當前不在內存中的頁時,就會出現 頁面錯誤,導致內存管理程 序從分頁文件中檢索該頁並放到主存中,這個任務稱為 分頁。決定將哪些頁交換出去的具體 算法取決於所用的 Windows 版本,可能是最近最少訪問算法的一種變體。同樣要注意, Windows 允許進程間共享頁幀,比如 DLL 分配的頁幀,常常被多個應用程序同時使用。 Windows 通過將來自不同地址空間的多個虛擬頁映射到同一個物理地址來實現這種機制。

應用程序很高興對所有這些活動一無所知。它只知道自己的虛擬地址空間。但是,如果當 前在主存中的頁面集(稱為 駐留集)少於實際要使用的頁面集(稱為 工作集),應用程序 的性能很快就會顯著降低。(不幸的是,本文中您將看到,我們要討論的工具常常交換使用 這兩個術語,盡管它們指的是完全不同的事物。)

Task Manager 和 PerfMon

我們首先考察兩種最常見的工具:Task Manager 和 PerfMon。這兩個工具都隨 Windows 一起提供,因此由此起步比較容易。

Task Manager

Task Manager 是一種非常見的 Windows 進程監控程序。您可以通過熟悉的 Ctrl-Alt- Delete 組合鍵來啟動它,或者右擊任務欄。Processes 選項卡顯示了最詳細的信息,如圖 2 所示。

圖 2. Task Manager 進程選項卡

圖 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 術語表(請參閱 參考資料)中找到這些術語的定義。 工作集 更通 常的含義指的是一個邏輯概念,即在某一點上為了避免分頁操作,進程需要駐留在內存中的 那些頁面。

PerfMon

隨 Windows 一起提供的另一種 Microsoft 工具是 PerfMon,它監控各種各樣的計數器, 從打印隊列到電話。PerfMon 通常在系統路徑中,因此可以在命令行中輸入 perfmon 來啟動 它。這個工具的優點是以圖形化的方式顯示計數器,很容易看到計數器隨時間的變化情況。

請在 PerfMon 窗口上方的工具欄中單擊 + 按鈕,這樣會打開一個對話框讓您選擇要監控 的計數器,如圖 3a 所示。計數器按照 性能對象分成不同的類別。與內存使用關系最密切的 兩個類是 Memory 和 Process。選中計數器然後單擊 Explain 按鈕,就可以看到計數器的定 義。說明出現在主對話框下方彈出的單獨的窗口中,如圖 3b 所示。

圖 3a. PerfMon 計數器窗口

圖 3b. 說明

選擇感興趣的計數器(使用 Ctrl 可以選中多行)和要監控的實例(所分析的應用程序的 Java 進程),然後單擊 Add 按鈕。工具立刻開始顯示選擇的所有計數器的值。您可以選擇 用報告、圖表或者直方圖來顯示這些值。圖 4 顯示的是一個直方圖。

圖 4. PerfMon 直方圖

如果圖中什麼也看不到,表明您可能需要改變比例,右擊圖形區域,選擇 Properties 然 後切換到 Graph 選項卡。也可以到計數器的 Data 選項卡改變某個計數器的比例。

要觀察的計數器

不幸的是,PerfMon 使用了與 Task Manager 不同的術語。表 1 列出了最常用的計數器 ,如果有的話,還給出了相應的 Task Manager 功能:

表 1. 常用的 PerfMon 內存計數器

計數器名 類別 說明 等價的 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 是我們要介紹的第一個可以觀察進程地址空間內容的工具(請參閱 參考資料) 。該工具不僅能用於觀察內存占用,還可以設置優先級和殺死進程,還有一個很有用的命令 行版本,用來列出機器上所有進程的屬性。但我們要介紹的如何使用它觀察內存占用情況。

啟動 PrcView 會看到一個類 Task Manager 的視圖,它顯示了系統中的進程。如果滾動 窗口並選中一個 Java 進程,屏幕就會如圖 5 所示。

圖 5. 啟動後的 PrcView 窗口

右擊該 Java 進程打開彈出菜單,或者從上方的菜單條中選擇 Process,就可以看到該進 程的一些情況,比如它擁有的線程、加載的 DLL,也可以殺死該進程或者設置其優先級。我 們所關心的是考察其內存占用,打開如圖 6 所示的窗口。

圖 6. 觀察進程的內存

現在我們分析一下 PrcView 顯示的地址空間映射的前幾行。第一行表明從地址 0 開始, 有一塊長度為 65,536 (64K) 的內存是自由空間。這些地址什麼也沒有分配,也不能用於尋 址。第二行說明緊跟在後面,從地址 0x00010000 起,有一個長為 8,192 字節(兩個 4K 頁 面)的提交內存,即可以尋址並且得到分頁文件中的頁幀支持的內存。然後是一段自由空間 ,另一段提交空間,如此等等。

碰巧的是,這些地址空間區域對您來說沒有什麼意義,因為它是供 Windows 使用的。描 述 Windows 地址空間的 Microsoft 文檔指出,這些不同的區域是為兼容 MS-DOS 保留的用 戶數據和代碼所用的區域從 4MB 開始(請參閱 參考資料)。

向下滾動窗口,最終會看到某些您能夠清楚識別的地址空間,如圖 7 所示。

圖 7. Java 堆

圖 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

TopToBottom 可以從 smidgeonsoft.com 免費獲得(請參閱 參考資料)。該工具沒有任 何文檔,但是為當前執行進程提供了一組完備的視圖。您不僅能夠按名稱進程 ID 排序,還 能夠按起動時間排序,如果需要了解計算機上程序的啟動順序這一點可能很有用。

圖 8 所示的 TopToBottom 窗口中,進程是按創建時間排序的( View --> Sort -- > Creation Time)。

圖 8. TopToBottom,進程按創建時間排序

StartUp 選項卡顯示了創建 Java 進程的進程、開始的時間和日期、所用的命令行以及可 執行文件和當前目錄的完整路徑。也可以單擊 Environment 選項卡顯示啟動時傳遞給該進程 的所有環境變量的值。Modules 選項卡顯示了 Java 進程所用的 DLL,如圖 9 所示。

圖 9. TopToBottom Modules 選項卡

同樣可以按照不同的方式對列表進行排序。在圖 9 中,它們是按照初始化順序排列的。 如果雙擊其中的一行,可以看到 DLL 的詳細信息:其地址和大小、編寫的日期和時間、所依 賴的其他 DLL 列表以及加載該 DLL 的所有運行中的進程列表。如果研究這個列表,就會發 現有的 DLL 是每個運行的進程都要用到的,比如 NTDLL.DLL;有的在所有 Java 進程間共享 ,比如 JVM.DLL;而另有一些可能只有一個進程使用。

通過累加各個 DLL 的大小就可以計算出進程所用 DLL 的總大小。但是得到的結果可能會 造成誤解,因為它並不意味著進程要消費所有這些內存占用。真正的大小取決於進程實際使 用了 DLL 的哪些部分。這些部分將進入進程的工作集。雖然很明顯,但還是要注意 DLL 是 只讀的和共享的。如果大量進程都使用一個給定的 DLL,同一時刻只有一組實際內存頁保存 DLL 數據。這些實際的頁面可以映射到不同的地址,進入使用它們的那些進程。Task Manager 之類的工具將工作集看作是共享和非共享頁面的總和,因此很難確定使用 DLL 對內 存占用的影響。模塊信息是一種很有用的方式,提供了“最差情況下”由於 DLL 造成的內存占用,需要的話可以使用其他工具作更詳盡地分析。

我們關心的是內存占用情況,請單擊 Memory 選項卡,圖 10 顯示了 Java 程序所用內存 的一小部分。

圖 10. TopToBottom Memory 選項卡

顯示的內容和 PrcView 類似,但是它僅僅顯示了虛擬空間中的提交內存,而沒有保留內 存。但是它有兩個優點。首先,它可以更詳盡地描述頁面。比如在圖 10 中專門標記了 Thread 3760 棧區域,而不僅僅是一些讀/寫數據。它是別的其他數據區包括環境、進程參數 、進程堆、線程棧和線程環境塊(TEB)。其次,您可以直接在 TopToBottom 中浏覽甚至搜 索內存。您可以搜索文本字符串或者最多 16 字節的十六進制序列。可以將十六進制搜索限 制在特定的序列中,在檢索地址引用時這一點很方便。

TopToBottom 也有快照功能,可以把進程的所有信息轉儲到剪貼板中。

VADump

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 Process Explorer

更有用的內存分析工具來自 Sysinternals 公司(請參閱 參考資料)。其中一個工具是 圖形化的進程管理器,如圖 11 所示,它可以作為 Task Manager 的高級代替品。

圖 11. Process Explorer 進程樹

Process Explorer 具有和 Task Manager 相同的功能。比方說,您可以得到整個系統性 能的動態圖形(通過 View --> System Information...),也可用類似的方式配置主進 程視圖中的列。在 Process --> Properties... 中,Process Explorer 提供了進程的更 多信息,比如完整路徑和命令行、線程、CPU 實用的動態圖表和私有內存。它的用戶界面非 常好,如圖 11 所示。它還可以觀察 DLL 的信息和進程的句柄。您可以使用 Options -- > Replace Task Manager 用 Process Explorer 代替默認的 Task Manager。

Sysinternals ListDLLs

還可以從 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。

Sysinternals Handle

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 \KnownDlls
7fc: KeyedEvent \KernelObjects\CritSecOutOfMemoryEvent

如果關心內存的使用,句柄是一個重要因素,因為每個句柄都要消耗一些空間。具體的數 量取決於操作系統版本和句柄的類型。一般而言,句柄不應該對內存占用產生很大影響。只 要數一數該工具輸出的行數,就可以判定句柄是不是太多,或者是否還在增長。無論出現哪 種情況,都值得注意,建議進行更細致的分析。

技巧和竅門

現在您已經操作(不是雙關語,handle 還有一個含義是句柄)了我們要介紹的所有工具 ,下面是您單獨或一起使用這些工具,改進內存監控的一些方法。

尋找進程 ID

為了找到應用程序的進程 ID,以便在 VADump 這樣的命令行工具中使用,請在 Task Manager 中打開 Applications 選項卡右擊所關心的進程。選擇 Go To Process,這樣就會 在 Processes 選項卡中看到對應的 ID。

確定一個 Java 進程

是否對那些都命名為 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 堆獲取了內存的一大部分,您需要深入分析代碼,確定對象引用是否超出了必要的時間 。這方面有更多的工具和文章可以提供幫助, 參考資料部分給出了一些有用的鏈接,可以為 您指出正確的方向。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved