盡管 Java? 運(yùn)行時能夠解決大量的內(nèi)存管理問題,但對程序的內(nèi)存占用情況保持警惕仍然是優(yōu)化機(jī)器性能、測定內(nèi)存泄露的關(guān)鍵。Windows 上有很多工具可以監(jiān)控內(nèi)存的使用。但每種工具各有長短,都有特定的傾向性,常常沒有明確地定義自己測量的是什么。作者將澄清關(guān)于內(nèi)存使用的一些常見誤解, 介紹很多有用的工具,同時還將提供何時以及如何使用它們的指南。<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
Java 技術(shù)最知名的一個優(yōu)點是:與其他語言如 C 程序員不同,Java 程序員不需要對令人畏懼的內(nèi)存分配和釋放負(fù)責(zé)。Java 運(yùn)行庫可以為您管理這些任務(wù)。每個實例化的對象都自動在堆中分配內(nèi)存,垃圾收集程序定期收回不再使用的對象所占據(jù)的內(nèi)存。但是您還不能完全撒手不管。您仍 然需要監(jiān)控程序的內(nèi)存使用情況,因為 Java 進(jìn)程的內(nèi)存不僅僅包括堆中分配的對象。它還包括程序的字節(jié)碼(JVM 在運(yùn)行時解釋執(zhí)行的指令)、JIT 代碼(已經(jīng)為目標(biāo)處理器編譯過的代碼)、任何本機(jī)代碼和 JVM 使用的一些元數(shù)據(jù)(異常表、行號表等等)。情況更為復(fù)雜的是,某些類型的內(nèi)存(如本機(jī)庫)可以在進(jìn)程間共享,因此確定 Java 應(yīng)用程序的內(nèi)存占用可能是一項非常艱巨的任務(wù)。
有大量在 Windows 監(jiān)控內(nèi)存使用的工具,但不幸的是沒有一種能夠提供您需要的所有信息。更糟的是,這些形形色色的工具甚至沒有一個公共的詞匯表。但本文會助您一臂之力,文中將介紹一些最有用的、可免費獲得的工具,并提供了如何使用它們的技巧。
Windows 內(nèi)存:一次旋風(fēng)般的旅行
了解本文要討論的工具之前,需要對 Windows 如何管理內(nèi)存有基本的理解。Windows 使用一種 分頁請求虛擬內(nèi)存 系統(tǒng),現(xiàn)在我們就來分析一下這種系統(tǒng)。
虛擬內(nèi)存的概念在上個世紀(jì)五十年代就提出了,當(dāng)時是作為解決不能一次裝入實際內(nèi)存的程序這一復(fù)雜問題的方案提出的。在虛擬內(nèi)存系統(tǒng)中,程序可以訪問超出可用物理內(nèi)存的更大的地址集合,專用內(nèi)存管理程序?qū)⑦@些邏輯地址映射到實際地址,使用磁盤上的臨時存儲保存超出的部分。
Windows 所使用的現(xiàn)代虛擬內(nèi)存實現(xiàn)中,虛擬存儲被組織成大小相同的單位,稱為 頁 。每個操作系統(tǒng)進(jìn)程占用自己的 虛擬地址空間 ,即一組可以讀寫的虛擬內(nèi)存頁。每個頁可以有三種狀態(tài):
-
自由
:還沒有進(jìn)程使用這部分地址空間。如果企圖訪問這部分空間,無論讀寫都會造成某種運(yùn)行時失效。該操作將導(dǎo)致彈出一個 Windows 對話框,提示出現(xiàn)了訪問沖突。(Java 程序不會造成這種錯誤,只有用支持指針的語言編寫的程序才可能造成這種問題。)
-
保留
:這部分地址空間保留給進(jìn)程,以供將來使用,但是在交付之前,不能訪問該地址空間。很多 Java 堆在一開始處于保留狀態(tài)。
- 提交 :程序可以訪問的內(nèi)存,得到了完全 支持 ,就是說已經(jīng)在分頁文件中分配了頁幀。提交的頁只有在第一次被引用時才裝入主存,因此成為 請求式分頁 。
圖 1 說明了進(jìn)程地址空間中的虛擬頁如何映射到內(nèi)存中的物理頁幀。
圖 1. 進(jìn)程地址空間中的虛擬頁到物理頁幀的映射

如 果運(yùn)行的是 32 位機(jī)器(如一般的 Intel 處理器),那么進(jìn)程的整個虛擬地址空間就是 4GB,因為這是用 32 位所能尋址的最大地址空間。Windows 通常不會允許您訪問地址空間中的所有這些內(nèi)存,進(jìn)程自己使用的只有不到一半,其他供 Windows 使用。這 2 GB 的私有空間部分包含了 JVM 執(zhí)行程序所需要的多數(shù)內(nèi)存:Java 堆、JVM 本身的 C 堆、用于程序線程的棧、保存字節(jié)碼和即時編譯方法的內(nèi)存、本機(jī)方法所分配的內(nèi)存等等。后面介紹地址空間映射時,我們將描述這些不同的部分。
希望分配了大量連續(xù)內(nèi)存區(qū)域但這些內(nèi)存不馬上同時使用的程序常常結(jié)合使用保留內(nèi)存和提交內(nèi)存。JVM 以這種方式分配 Java 堆。參數(shù)
-mx
告訴 JVM 堆有多大,但 JVM 通常不在一開始就分配所有這些內(nèi)存。它
保留
-mx
所規(guī)定的大小,標(biāo)記能夠提交的整個地址范圍。然后它僅僅提交一部分內(nèi)存,這也是內(nèi)存管理程序需要在實際內(nèi)存和分頁文件中分配頁來支持它們的那一部分。以后 活動數(shù)據(jù)數(shù)量增加,堆需要擴(kuò)展,JVM 可以再提交多一點內(nèi)存,這些內(nèi)存與當(dāng)前提交的部分相鄰。通過這種方式,JVM 可以維護(hù)單一的、連續(xù)的堆空間,并根據(jù)需要增長(關(guān)于如何使用 JVM 堆參數(shù)請參閱
參考資料
)。
物理存儲頁組織成大小相同的單位,通常稱為 頁幀 。操作系統(tǒng)有一種數(shù)據(jù)結(jié)構(gòu)稱為 頁表 ,將應(yīng)用程序訪問的虛擬頁映射到主存中的實際頁幀。沒有裝入的頁保存在磁盤上的臨時分頁文件中。當(dāng)應(yīng)用程序要訪問當(dāng)前不在內(nèi)存中的頁時,就會出現(xiàn) 頁面錯誤 ,導(dǎo)致內(nèi)存管理程序從分頁文件中檢索該頁并放到主存中,這個任務(wù)稱為 分頁 。 決定將哪些頁交換出去的具體算法取決于所用的 Windows 版本,可能是最近最少訪問算法的一種變體。同樣要注意,Windows 允許進(jìn)程間共享頁幀,比如 DLL 分配的頁幀,常常被多個應(yīng)用程序同時使用。Windows 通過將來自不同地址空間的多個虛擬頁映射到同一個物理地址來實現(xiàn)這種機(jī)制。
應(yīng)用程序很高興對所有這些活動一無所知。它只知道自己的虛擬地址空間。但是,如果當(dāng)前在主存中的頁面集(稱為 駐留集 )少于實際要使用的頁面集(稱為 工作集 ),應(yīng)用程序的性能很快就會顯著降低。(不幸的是,本文中您將看到,我們要討論的工具常常交換使用這兩個術(shù)語,盡管它們指的是完全不同的事物。)
![]() ![]() |
![]() |
我們首先考察兩種最常見的工具:Task Manager 和 PerfMon。這兩個工具都隨 Windows 一起提供,因此由此起步比較容易。
Task Manager 是一種非常見的 Windows 進(jìn)程監(jiān)控程序。您可以通過熟悉的 Ctrl-Alt-Delete 組合鍵來啟動它,或者右擊任務(wù)欄。Processes 選項卡顯示了最詳細(xì)的信息,如圖 2 所示。
圖 2. Task Manager 進(jìn)程選項卡

圖 2 中顯示的列已經(jīng)通過選擇 View --> Select Columns 作了調(diào)整。有些列標(biāo)題非常含糊,但可以在 Task Manager 幫助中找到各列的定義。和進(jìn)程內(nèi)存使用情況關(guān)系最密切的計數(shù)器包括:
-
Mem Usage(內(nèi)存使用)
:在線幫助將其稱為進(jìn)程的工作集(盡管很多人稱之為駐留集)——當(dāng)前在主存中的頁面集。但是這個數(shù)值包含能夠和其他進(jìn)程共享的頁面,因此要注意避免重復(fù)計算。比方說,如果要計算共享同一個 DLL 的兩個進(jìn)程的總內(nèi)存占用情況,不能簡單地把“內(nèi)存使用”值相加。
-
Peak Mem Usage(內(nèi)存使用高峰值)
:進(jìn)程啟動以來 Mem Usage(內(nèi)存使用)字段的最大值。
-
Page Faults(頁面錯誤)
:進(jìn)程啟動以來要訪問的頁面不在主存中的總次數(shù)。
- VM Size(虛擬內(nèi)存大?。? :聯(lián)機(jī)幫助將其稱為“分配給進(jìn)程私有虛擬內(nèi)存總數(shù)。”更確切地說,這是進(jìn)程所 提交 的內(nèi)存。如果進(jìn)程保留內(nèi)存而沒有提交,那么該值就與總地址空間的大小有很大的差別。
雖然 Windows 文檔將 Mem Usage(內(nèi)存使用)稱為工作集,但在該上下文中,它實際上指的是很多人所說的駐留集(resident set),明白這一點很重要。您可以在 Memory Management Reference 術(shù)語表(請參閱 參考資料 )中找到這些術(shù)語的定義。 工作集 更通常的含義指的是一個邏輯概念,即在某一點上為了避免分頁操作,進(jìn)程需要駐留在內(nèi)存中的那些頁面。
隨 Windows 一起提供的另一種 Microsoft 工具是 PerfMon,它監(jiān)控各種各樣的計數(shù)器,從打印隊列到電話。PerfMon 通常在系統(tǒng)路徑中,因此可以在命令行中輸入
perfmon
來啟動它。這個工具的優(yōu)點是以圖形化的方式顯示計數(shù)器,很容易看到計數(shù)器隨時間的變化情況。
請在 PerfMon 窗口上方的工具欄中單擊 + 按鈕,這樣會打開一個對話框讓您選擇要監(jiān)控的計數(shù)器,如圖 3a 所示。計數(shù)器按照 性能對象 分成不同的類別。與內(nèi)存使用關(guān)系最密切的兩個類是 Memory 和 Process 。選中計數(shù)器然后單擊 Explain 按鈕,就可以看到計數(shù)器的定義。說明出現(xiàn)在主對話框下方彈出的單獨的窗口中,如圖 3b 所示。
圖 3a. PerfMon 計數(shù)器窗口

圖 3b. 說明

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

如果圖中什么也看不到,表明您可能需要改變比例,右擊圖形區(qū)域,選擇 Properties 然后切換到 Graph 選項卡。也可以到計數(shù)器的 Data 選項卡改變某個計數(shù)器的比例。
要觀察的計數(shù)器
不幸的是,PerfMon 使用了與 Task Manager 不同的術(shù)語。表 1 列出了最常用的計數(shù)器,如果有的話,還給出了相應(yīng)的 Task Manager 功能:
表 1. 常用的 PerfMon 內(nèi)存計數(shù)器
計數(shù)器名 | 類別 | 說明 | 等價的 Task Manager 功能 |
Working Set | Process | 駐留集,當(dāng)前在實際內(nèi)存中有多少頁面 | Mem Usage |
Private Bytes | Process | 分配的私有虛擬內(nèi)存總數(shù),即提交的內(nèi)存 | VM Size |
Virtual Bytes | Process | 虛擬地址空間的總體大小,包括共享頁面。因為包含保留的內(nèi)存,可能比前兩個值大很多 | -- |
Page Faults / sec(每秒鐘內(nèi)的頁面錯誤數(shù)) | Process(進(jìn)程) | 每秒中出現(xiàn)的平均頁面錯誤數(shù) | 鏈接到 Page Faults(頁面錯誤),顯示頁面錯誤總數(shù) |
Committed Bytes(提交的字節(jié)數(shù)) | Memory(內(nèi)存) | “提交”狀態(tài)的虛擬內(nèi)存總字節(jié)數(shù) | -- |
您可以下載并運(yùn)行我們用 C 編寫的一個小程序(請參閱
下載
部分),來觀察 Task Manager 和 PerfMon 中顯示的這些數(shù)量。該程序首先調(diào)用 Windows
VirtualAlloc
保留內(nèi)存,然后再提交這些內(nèi)存,最后使用其中一些內(nèi)存,每 4,096 個字節(jié)寫入一個值,從而將頁面代入工作集。如果運(yùn)行該例子,并使用 Task Manager 或 PerfMon 觀察,就會發(fā)現(xiàn)這些值的變化情況。
![]() ![]() |
![]() |
現(xiàn)在已經(jīng)看到了應(yīng)用程序使用多少內(nèi)存,還需要深入分析內(nèi)存的實際內(nèi)容。這一節(jié)介紹一些更加復(fù)雜的工具,討論什么時候適用輸出結(jié)果,以及如何解釋這些結(jié)果。
PrcView 是我們要介紹的第一個可以觀察進(jìn)程地址空間內(nèi)容的工具(請參閱 參考資料 )。該工具不僅能用于觀察內(nèi)存占用,還可以設(shè)置優(yōu)先級和殺死進(jìn)程,還有一個很有用的命令行版本,用來列出機(jī)器上所有進(jìn)程的屬性。但我們要介紹的如何使用它觀察內(nèi)存占用情況。
啟動 PrcView 會看到一個類 Task Manager 的視圖,它顯示了系統(tǒng)中的進(jìn)程。如果滾動窗口并選中一個 Java 進(jìn)程,屏幕就會如圖 5 所示。
圖 5. 啟動后的 PrcView 窗口

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

現(xiàn) 在我們分析一下 PrcView 顯示的地址空間映射的前幾行。第一行表明從地址 0 開始,有一塊長度為 65,536 (64K) 的內(nèi)存是自由空間。這些地址什么也沒有分配,也不能用于尋址。第二行說明緊跟在后面,從地址 0x00010000 起,有一個長為 8,192 字節(jié)(兩個 4K 頁面)的提交內(nèi)存,即可以尋址并且得到分頁文件中的頁幀支持的內(nèi)存。然后是一段自由空間,另一段提交空間,如此等等。
碰巧的是,這些地址空間區(qū)域?qū)δ鷣碚f沒有什么意義,因為它是供 Windows 使用的。描述 Windows 地址空間的 Microsoft 文檔指出,這些不同的區(qū)域是為兼容 MS-DOS 保留的用戶數(shù)據(jù)和代碼所用的區(qū)域從 4MB 開始(請參閱 參考資料 )。
向下滾動窗口,最終會看到某些您能夠清楚識別的地址空間,如圖 7 所示。
圖 7. Java 堆

圖 7 中高亮顯示的行及其后的一行對應(yīng) Java 堆。我們給這里啟動的 Java 進(jìn)程 1000MB 大小的堆(使用
-mx1000m
), 對于該程序而言,這個堆太大了,但這樣在 PrcView 映射中更加清楚。高亮顯示的一行說明堆的提交部分只有 4MB,從地址 0x10180000 開始。緊隨在后面的一行顯示了一個很大的保留區(qū)域,這是堆中還沒有提交的那一部分。在啟動過程中,JVM 首先保留出完整的 1000MB 空間(從 0x10180000 到 0x4e980000 范圍之內(nèi)的地址都不能用),然后提交啟動過程所需要的那一部分,該例中為 4MB。為了驗證該值確實對應(yīng)當(dāng)前的堆大小,您可以用
-verbosegc
JVM 選項調(diào)用該 Java 程序,可以打印出垃圾收集程序中的詳細(xì)信息。從下面的
-verbosegc
輸出中第二個 GC 的第二行可以看出,當(dāng)前的堆大小大約是 4MB:
>java -mx1000m -verbosegc Hello |
-verbosegc
的輸出格式取決于所用的 JVM 實現(xiàn),請參閱
參考資料
中關(guān)于 IBM JVM 的相關(guān)文章,或者參考供應(yīng)商的文檔。
如果活動數(shù)據(jù)的數(shù)量增加,JVM 需要將堆的大小擴(kuò)展到 4MB 之外,它就會提交稍微多一點的保留區(qū)域。就是說,新的區(qū)域可以從 0x10580000 開始,與已經(jīng)提交的堆空間連接在一起。
在 圖 7 所示的 PrcView 窗口中,最下面一行的三個總數(shù)給出了進(jìn)程提交的總內(nèi)存,這些數(shù)據(jù)是根據(jù)第七列 Type 計算得到的。三個總數(shù)為:
- Private :分頁文件支持的提交內(nèi)存。
- Mapped :直接映射到文件系統(tǒng)的提交內(nèi)存。
- Image :屬于可執(zhí)行代碼的提交內(nèi)存,包括啟動的執(zhí)行文件和 DLL。
到目前為止,我們只是在根據(jù)大小來了解堆在地址空間中的分配情況。為了更好的理解其他一些內(nèi)存區(qū)域,如果能夠觀察內(nèi)存的內(nèi)部情形,會對您的了解很有幫助。這就要用到下面將討論的工具 TopToBottom。
TopToBottom 可以從 smidgeonsoft.com 免費獲得(請參閱 參考資料 )。該工具沒有任何文檔,但是為當(dāng)前執(zhí)行進(jìn)程提供了一組完備的視圖。您不僅能夠按名稱進(jìn)程 ID 排序,還能夠按起動時間排序,如果需要了解計算機(jī)上程序的啟動順序這一點可能很有用。
圖 8 所示的 TopToBottom 窗口中,進(jìn)程是按創(chuàng)建時間排序的( View --> Sort --> Creation Time )。
圖 8. TopToBottom,進(jìn)程按創(chuàng)建時間排序

StartUp 選項卡顯示了創(chuàng)建 Java 進(jìn)程的進(jìn)程、開始的時間和日期、所用的命令行以及可執(zhí)行文件和當(dāng)前目錄的完整路徑。也可以單擊 Environment 選項卡顯示啟動時傳遞給該進(jìn)程的所有環(huán)境變量的值。Modules 選項卡顯示了 Java 進(jìn)程所用的 DLL,如圖 9 所示。
圖 9. TopToBottom Modules 選項卡

同 樣可以按照不同的方式對列表進(jìn)行排序。在圖 9 中,它們是按照初始化順序排列的。如果雙擊其中的一行,可以看到 DLL 的詳細(xì)信息:其地址和大小、編寫的日期和時間、所依賴的其他 DLL 列表以及加載該 DLL 的所有運(yùn)行中的進(jìn)程列表。如果研究這個列表,就會發(fā)現(xiàn)有的 DLL 是每個運(yùn)行的進(jìn)程都要用到的,比如 NTDLL.DLL;有的在所有 Java 進(jìn)程間共享,比如 JVM.DLL;而另有一些可能只有一個進(jìn)程使用。
通過累加各個 DLL 的大小就可以計算出進(jìn)程所用 DLL 的總大小。但是得到的結(jié)果可能會造成誤解,因為它并不意味著進(jìn)程要消費所有這些內(nèi)存占用。真正的大小取決于進(jìn)程實際使用了 DLL 的哪些部分。這些部分將進(jìn)入進(jìn)程的工作集。雖然很明顯,但還是要注意 DLL 是只讀的和共享的。如果大量進(jìn)程都使用一個給定的 DLL,同一時刻只有一組實際內(nèi)存頁保存 DLL 數(shù)據(jù)。這些實際的頁面可以映射到不同的地址,進(jìn)入使用它們的那些進(jìn)程。Task Manager 之類的工具將工作集看作是共享和非共享頁面的總和,因此很難確定使用 DLL 對內(nèi)存占用的影響。模塊信息是一種很有用的方式,提供了“最差情況下”由于 DLL 造成的內(nèi)存占用,需要的話可以使用其他工具作更詳盡地分析。
我們關(guān)心的是內(nèi)存占用情況,請單擊 Memory 選項卡,圖 10 顯示了 Java 程序所用內(nèi)存的一小部分。
圖 10. TopToBottom Memory 選項卡

顯 示的內(nèi)容和 PrcView 類似,但是它僅僅顯示了虛擬空間中的提交內(nèi)存,而沒有保留內(nèi)存。但是它有兩個優(yōu)點。首先,它可以更詳盡地描述頁面。比如在圖 10 中專門標(biāo)記了 Thread 3760 棧區(qū)域,而不僅僅是一些讀/寫數(shù)據(jù)。它是別的其他數(shù)據(jù)區(qū)包括環(huán)境、進(jìn)程參數(shù)、進(jìn)程堆、線程棧和線程環(huán)境塊(TEB)。其次,您可以直接在 TopToBottom 中瀏覽甚至搜索內(nèi)存。您可以搜索文本字符串或者最多 16 字節(jié)的十六進(jìn)制序列??梢詫⑹M(jìn)制搜索限制在特定的序列中,在檢索地址引用時這一點很方便。
TopToBottom 也有快照功能,可以把進(jìn)程的所有信息轉(zhuǎn)儲到剪貼板中。
VADump 是一種方便的命令行工具,屬于 Microsoft ? Platform SDK 包(請參閱 參考資料 )的一部分。它的目的是轉(zhuǎn)儲特定進(jìn)程的虛擬地址空間和駐留集。使用 VADump 最簡單的方法就是在命令行中輸入以下命令:
vadump |
process_id
是要分析的進(jìn)程號。如果不帶參數(shù),則可以顯示 VADump 完整的用法說明。我們建議您將結(jié)果通過管道保存到文件中(如
vadump 1234 > output.txt
),因為 VADump 生成的信息非常多,一屏放不下。
輸出中首先給出進(jìn)程虛擬地址空間的索引:
>vadump -p 3904 |
(為便于閱讀,省略了部分行。)
對于每個塊,都可以看到下列信息:
- Address :十六進(jìn)制格式,相對于進(jìn)程虛擬地址空間起始位置的偏移量。
- Size :字節(jié)數(shù),用十六進(jìn)制表示。
- State :自由、保留或提交。
- Protection status :只讀或/讀寫。
- Type :私有(不能被其他進(jìn)程訪問)、映射(直接來自文件系統(tǒng))或鏡像(可執(zhí)行代碼)。
然后列出進(jìn)程使用的所有 DLL 及其大小,后面是工作集和分頁文件使用的統(tǒng)計信息。
目前為止,所提到的信息都可以從其他工具獲得。但是通過 VADump 的
-o
選項還可以得到更有啟發(fā)作用的輸出結(jié)果。它可以生成當(dāng)前工作集的快照(某一給定時刻實際存在于主存中的頁面)。關(guān)于該選項文檔沒有提供多少信息,但是在確 定駐留集中最重要的部分時,這是一個極其有用的工具,這樣能夠確定最可能的內(nèi)存優(yōu)化目標(biāo)。通過定期記錄內(nèi)存快照,您還可以使用它確定是否存在內(nèi)存泄漏。這 種模式下,輸出從虛擬地址空間中提交頁面的詳盡轉(zhuǎn)儲開始,無論這些頁面是否還在主存中:
>vadump -o -p 3904 |
滾動到這個長長的列表的最后,您會看到更有趣的信息:目前駐留主存的進(jìn)程頁面的頁表映射清單:
0xC0000000 > (0x00000000 : 0x003FFFFF) 132 Resident Pages |
每個映射都對應(yīng)頁表中的一項,組成了進(jìn)程工作集另一個 4KB。但要從這些映射中發(fā)現(xiàn)應(yīng)用程序的哪些部分使用了最多的內(nèi)存仍然很困難,但幸運(yùn)的是下一部分輸出給出了有用的總結(jié):
Category Total Private Shareable Shared |
最有趣的兩個值通常是 Heap (即 Windows 進(jìn)程堆)和 Other Data。直接通過調(diào)用 Windows API 分配的內(nèi)存組成了進(jìn)程堆部分,Other Data 中包括 Java 堆。Grand Total Working Set 對應(yīng) Task Manager 的 Mem Usage 和 TEB 字段(進(jìn)程的線程環(huán)境塊所需要的內(nèi)存,TEB 是一種 Windows 內(nèi)部結(jié)構(gòu))。
最后,在
VADump -o
輸出的最下端總結(jié)了 DLL、堆和線程棧對工作集的相對貢獻(xiàn):
Module Working Set Contributions in pages |
通過這種模式還可以用 VADump 獲得兩個或更多 Java 進(jìn)程的總和內(nèi)存占用情況(請參閱本文后面的 技巧和竅門 )。
更有用的內(nèi)存分析工具來自 Sysinternals 公司(請參閱 參考資料 )。其中一個工具是圖形化的進(jìn)程管理器,如圖 11 所示,它可以作為 Task Manager 的高級代替品。
圖 11. Process Explorer 進(jìn)程樹

Process Explorer 具有和 Task Manager 相同的功能。比方說,您可以得到整個系統(tǒng)性能的動態(tài)圖形(通過 View --> System Information... ),也可用類似的方式配置主進(jìn)程視圖中的列。在 Process --> Properties... 中,Process Explorer 提供了進(jìn)程的更多信息,比如完整路徑和命令行、線程、CPU 實用的動態(tài)圖表和私有內(nèi)存。它的用戶界面非常好,如圖 11 所示。它還可以觀察 DLL 的信息和進(jìn)程的句柄。您可以使用 Options --> Replace Task Manager 用 Process Explorer 代替默認(rèn)的 Task Manager。
還可以從 Sysinternals 下載兩個命令行工具:ListDLLs 和 Handle。如果希望在腳本或者程序中集成某種形式的內(nèi)存監(jiān)控,這兩個工具非常有用。
ListDLLs 用于觀察 DLL,DLL 可能造成很多內(nèi)存占用。使用之前請將其添加到路徑中,并使用幫助選項獲得用法說明。您可以用進(jìn)程 ID 或進(jìn)程名調(diào)用它。下面是我們的 Java 程序調(diào)用 DLL 的列表:
>listdlls -r 3904 |
也可以使用
listdlls -r java
命令,列出所有運(yùn)行的 Java 進(jìn)程及其使用的 DLL。
Handle 給出進(jìn)程所用句柄(文件、套接字等)的列表。解壓 Handle 下載文件,并將其添加到路徑中,然后試著運(yùn)行它。對于我們的 Java 程序,輸出結(jié)果如下所示:
>handle -p 3904 |
可以看出我們的進(jìn)程中有一個句柄,該句柄指向類路徑目錄和幾個 JAR 文件。事實上,該進(jìn)程還有更多的句柄,但默認(rèn)情況下該工具僅顯示引用文件的句柄。使用
-a
參數(shù)就可以顯示其他句柄:
>handle -a -p 3904 |
如果關(guān)心內(nèi)存的使用,句柄是一個重要因素,因為每個句 柄都要消耗一些空間。具體的數(shù)量取決于操作系統(tǒng)版本和句柄的類型。一般而言,句柄不應(yīng)該對內(nèi)存占用產(chǎn)生很大影響。只要數(shù)一數(shù)該工具輸出的行數(shù),就可以判定 句柄是不是太多,或者是否還在增長。無論出現(xiàn)哪種情況,都值得注意,建議進(jìn)行更細(xì)致的分析。
![]() ![]() |
![]() |
現(xiàn)在您已經(jīng)操作(不是雙關(guān)語,handle 還有一個含義是句柄)了我們要介紹的所有工具,下面是您單獨或一起使用這些工具,改進(jìn)內(nèi)存監(jiān)控的一些方法。
為了找到應(yīng)用程序的進(jìn)程 ID,以便在 VADump 這樣的命令行工具中使用,請在 Task Manager 中打開 Applications 選項卡右擊所關(guān)心的進(jìn)程。選擇 Go To Process ,這樣就會在 Processes 選項卡中看到對應(yīng)的 ID。
是 否對那些都命名為 Java 或 javaw 的進(jìn)程感到困惑,希望找出您要分析的那個進(jìn)程?如果從 IDE 或腳本中啟動 Java 進(jìn)程,要確定使用了哪一個 JVM 和發(fā)送給 Java 進(jìn)程的命令行參數(shù)可能很困難。這些信息可以在 TopToBottom Startup 選項卡中找到。您可以看到調(diào)用 JVM 使用的完整命令行和進(jìn)程啟動的時間。
是 否遇到過保存文件卻得到提示說文件正被另一個進(jìn)程使用的情況?或者嘗試關(guān)閉您認(rèn)為可靠的程序而得到錯誤消息的情況?您可以使用 SysInternals Process Explorer 工具的 Handle Search 功能發(fā)現(xiàn)誰在搗亂。只要打開 Search 對話框并輸入文件名即可。ProcExp 將遍歷所有打開的句柄,并確定相應(yīng)的進(jìn)程。最終常常會發(fā)現(xiàn),關(guān)閉用戶界面后,編輯器或者 Web 瀏覽器還留下一個小的存根進(jìn)程在運(yùn)行。
您可以使用 VADump 的
-o
選項獲得進(jìn)程當(dāng)前工作集的詳細(xì)視圖,以及有多少是共享的。獲得一個 Java 程序在系統(tǒng)上運(yùn)行的內(nèi)存轉(zhuǎn)儲,然后再啟動另一個并轉(zhuǎn)儲。只要比較每個結(jié)果的 Code/StaticData 部分,就會發(fā)現(xiàn)“Shareable”字節(jié)變成了“Shared”,從而稍微降低了內(nèi)存占用的增加。
Windows 實現(xiàn)了一種“清除”進(jìn)程駐留集的策略,在其看起來不再有用的時候予以清除。為了說明這一點,打開 Task Manager 的 Processes 選項框,便可以看到要監(jiān)控的應(yīng)用程序進(jìn)程,然后最小化應(yīng)用程序窗口,看看 Mem Usage 字段發(fā)生了什么變化!
對于 Windows Server 2003 和 Windows NT,Microsoft 提供了一個有趣的稱為 ClearMem 的工具,如果希望進(jìn)一步研究 Windows 下應(yīng)用程序使用內(nèi)存的情況,它可能非常有用(請參閱 參考資料 )。該工具確定了實際內(nèi)存的大小,分配足夠的內(nèi)存,很快地占用分配的內(nèi)存然后將其釋放。這樣就增加了其他應(yīng)用程序的內(nèi)存占用壓力,反復(fù)運(yùn)行 ClearMem 的結(jié)果是迫使應(yīng)用程序占用的內(nèi)存數(shù)量減少到最小。
![]() ![]() |
![]() |
本 文簡要介紹了 Windows 如何管理內(nèi)存,考察了一些最有用的免費工具,您可以用這些工具監(jiān)控 Java 應(yīng)用程序的內(nèi)存使用。無疑您還會發(fā)現(xiàn)和使用其他的工具,無論從 Web 上免費下載產(chǎn)品還是購買商業(yè)產(chǎn)品,我們都希望澄清相互矛盾的術(shù)語會對您有所幫助。通常要確定您測量的目標(biāo)的惟一方法就是做試驗,比如我們用于示范 Task Manager 的 VM Size(虛擬內(nèi)存大?。┖?Mem Usage(內(nèi)存使用)含義的 C 程序。
當(dāng)然這些工具只能幫助確定問題的所在,如何解決還要靠您自己。多數(shù)時候您會發(fā)現(xiàn) Java 堆獲取了內(nèi)存的一大部分,您需要深入分析代碼,確定對象引用是否超出了必要的時間。這方面有更多的工具和文章可以提供幫助, 參考資料 部分給出了一些有用的鏈接,可以為您指出正確的方向。
![]() ![]() |
![]() |
描述 名字 大小 下載方法 A C program to demonstrate how Windows uses memory
experiment.c | 3KB | HTTP |
![]() |
||
![]() |
關(guān)于下載方法的信息 |
![]() |
-
單擊本文頂端或底端的代碼按鈕(或者參閱
下載
部分),下載本文使用的測試程序。
-
請訪問
Memory Management Reference
,它提供了關(guān)于內(nèi)存管理的大量術(shù)語解釋、FAQ 和文章等。
-
Microsoft 開發(fā)人員網(wǎng)站提供了關(guān)于 Windows 內(nèi)存管理的說明,請參閱
About Memory Management
。
-
下載
PrcView
的最新版本,進(jìn)一步了解其功能。
-
可以從
www.smidgeonsoft.com
下載 TopToBottom 或其他 Windows 工具。
-
從 Microsoft 下載
VADump
,看一看
VADump 用戶參考
。
-
Process Explore、ListDLLs 和 Handle 可以從
SysInternals
網(wǎng)站免費獲得。
-
從 Microsoft Windows Server 2003 文檔站點了解
ClearMem
。
-
“
處理 Java 程序中的內(nèi)存漏洞
”(developerWorks,2001 年 2 月)提供了與堆有關(guān)的內(nèi)存問題的詳細(xì)介紹。
-
“
Java理論與實踐: 它是誰的對象?
”(developerWorks,2003 年 6 月)一文討論了跟蹤對象所有者避免內(nèi)存泄露的需要。
-
“
Sensible sanitation -- Understanding the IBM Java Garbage Collector, Part 3
”(developerWorks,2002 年 9 月)描述了 JVM 堆參數(shù)和
verbosegc
選項。
-
JInsight
是來自 IBM alphaWorks 的一種 Java 性能工具。
-
從
developerWorks Java 技術(shù)專區(qū)
中,可以找到數(shù)百篇 Java 技術(shù)資源。
![]() |
||
![]() |
Emma Shepherd 于 2002 年 Warwick 大學(xué)獲得計算機(jī)科學(xué)學(xué)士學(xué)位,畢業(yè)后就加入了 IBM。她的上一個項目是關(guān)于 Java Web 服務(wù)的,她還參與了 IBM WebSphere SDK for Web Services 和 Eclipse 插件的開發(fā)。業(yè)余時間她喜歡彈鋼琴,學(xué)說塞爾維亞語。 |
![]() |
||
![]() |
Martin Trotter 畢業(yè)于 Southampton 大學(xué),獲得了電氣方面的學(xué)位,幾年前剛加入 IBM。從一開始他就參與了 Java 的研究,在 JVM 和 JIT 方面擁有廣泛的經(jīng)驗,并曾領(lǐng)導(dǎo)過垃圾收集團(tuán)隊。其業(yè)余愛好包括制作陶器、散步、騎自行車兜風(fēng)和做木工等。 |
![]() |
||
![]() |
Caroline Maynard 從 Sussex 大學(xué)獲得了數(shù)學(xué)學(xué)位,畢業(yè)后一直在 IBM 工作。她最近領(lǐng)導(dǎo)了 IBM Java ORB 的開發(fā),對 Java 程序的內(nèi)存占用很感興趣。工作之余,她喜歡參加溫徹斯特交響樂隊的演出,從她的小貓爪子底下挽救小生物,陪著孩子到處旅游,有時間的時候,會列一列要做的 所有工作。 |
![]() |
||
![]() |
Matthew Peters 多年前從劍橋的女王學(xué)院獲得了數(shù)學(xué)學(xué)位,此后一直在 IBM 工作。最近幾年從事 IBM JVM 垃圾收集程序和 JVM 性能提升的研究。業(yè)余時間喜歡下圍棋,彈奏古典吉他,或者和家人騎車旅行 |
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
