Windows 的「記憶體使用量」代表什麼 ── 正確解讀 Working Set、Private Bytes、Commit 與分頁檔

· · Windows, Windows 開發, 記憶體管理, Working Set, Private Bytes, Commit, 分頁檔, 效能監控, 故障調查, Sysinternals

工作管理員顯示某個處理程序的「記憶體」是 1.2GB。然而在 Process Explorer 中 Working Set 是 1.5GB,Private Bytes 是 2.4GB,在 VMMap 中 Size 又更大。看系統整體,則顯示「已認可 19.6/31.8GB」。

那麼,這個應用程式到底用了幾 GB 的記憶體?

答案是:要看你想知道的是什麼,該檢視的數字就會不同。你想知道的是目前載入 RAM 的量、該處理程序專屬的配置量、系統承諾未來也會持續支撐的量,還是單純只是想知道保留了多大範圍的虛擬位址——不同的問題,要看的指標也不同。

Windows 的記憶體指標之所以難懂,是因為它們雖然都用「記憶體」這個相同的詞來顯示,實際上量測的卻是以下這些不同的軸向:

  • 用了多少位址空間
  • 消耗了多少 Commit
  • 目前是否常駐於實體 RAM
  • 該分頁是該處理程序專屬,還是可共用
  • 系統整體還能再支撐多少配置

本文以在 Windows 10/11 及現行 Windows Server 上,需要調查應用程式記憶體增長或系統整體記憶體不足的人為對象,把 Working Set、Private Working Set、Private Bytes、Commit、Virtual Bytes、分頁檔、Available、分頁錯誤之間的關係整理成一張全貌圖。

追查 .NET 物件無法被回收原因的步驟,請參考「在 .NET 中區分 GC 延遲與記憶體洩漏」;VMMap 與 Process Explorer 的具體操作,則整理在「Process Explorer / Handle / VMMap 實戰」中。本文專注於這些內容的前提——Windows 作業系統端數字的讀法

1. 先講結論

  • Working Set 是目前常駐於 RAM 的分頁。不只包含處理程序專屬的分頁,也包含 DLL 程式碼、記憶體對應檔案等可與其他處理程序共用的分頁。1
  • Private Working Set 是 Working Set 之中,目前僅屬於該處理程序的部分。可以用來近似「目前該處理程序專屬占用的 RAM」,但並非應用程式配置的全部量。2
  • Private Bytes 是該處理程序專屬的 Commit 量。這是與目前是否載入 RAM 不同的另一個指標。Win32 API 結構中的 PagefileUsage,在目前的 Windows 上實質上也代表同一個 Commit Charge,並非實際寫入分頁檔的位元組數。2
  • 工作管理員的「已認可 X/Y」中,X 是系統整體目前的 Commit 量,Y 是 Commit 上限。X 並不是分頁檔的使用量。Y 大致由 RAM 與分頁檔的總和決定。3
  • Reserve 與 Commit 是不同的。單純 Reserve 虛擬位址,只是先保留這段範圍供未來使用,並不會因此消耗等量的 RAM 或 Commit 上限。45
  • 分頁錯誤不一定等於磁碟 I/O。分頁錯誤分為可在 RAM 內解決的軟性錯誤,以及需要從分頁檔、執行檔、記憶體對應檔案等處讀取的硬性錯誤。16
  • 記憶體洩漏不是靠單次數值判斷,而是靠重複相同負載時的斜率判斷。特別要看 Private Bytes 及其細項是否在處理結束後仍階梯式持續增加,無法回到同一個穩定狀態。

若用一句話來總結:Working Set 是「目前 RAM 中的量」,Private Bytes 是「該處理程序專屬承諾的量」,Commit 則是「系統整體承諾的量」

選擇 Windows 主要記憶體指標依照想知道的是 RAM 常駐量、處理程序專屬 Commit 量、系統整體 Commit 量,還是虛擬位址範圍,該看的指標會不同目前 RAM 中的量處理程序專屬的承諾量系統整體的承諾量已保留的位址範圍想從「記憶體使用量」知道什麼Working SetPrivate BytesSystem CommitVirtual Bytes / Reserved常駐於實體 RAM處理程序專屬的 Commit與 Commit Limit 比較虛擬位址空間

圖1:先把「記憶體很多」這個觀察,拆解成4種不同的問題。

2. 把「記憶體使用量」拆成4個軸向

首先,不要把 Windows 的記憶體想成「一根柱子」,而是用4個軸向來思考。

用來分類一個分頁的4個獨立軸向分別確認虛擬位址的狀態、已認可分頁的後盾媒介、是否常駐於實體 RAM、是否可與其他處理程序共用用4個軸向看一個分頁位址狀態Free / Reserved / Committed後盾媒介以分頁檔為後盾 / 以檔案為後盾RAM 常駐常駐 / 未常駐可共用性Private / Shareable

圖2:即使是同一個分頁,位址狀態、後盾媒介、常駐狀態、可共用性也是分別決定的。

Mapped 並不是與 Free、Reserved、Committed 並列的位址狀態,而是一種區域類型。對應檢視(mapped view)的分頁也可能是 Committed。此外,Private 也不是後盾媒介的分類,而是可共用性的分類。因此,後盾媒介要分別讀成「以分頁檔為後盾」或「以檔案為後盾」,可共用性要分別讀成「Private」或「Shareable」。

把這4個軸向組合起來,代表性指標之間的關係如下。

分頁的狀態 Working Set Private Working Set Private Bytes Virtual Bytes 系
處理程序專屬、已認可、RAM 常駐 包含 包含 包含 包含
處理程序專屬、已認可、RAM 未常駐 不含 不含 包含 包含
DLL 或對應檔案的共用分頁、RAM 常駐 包含 原則上不含 原則上不含 包含
已保留但未認可 不含 不含 不含 可能包含
未使用的位址範圍 不含 不含 不含 通常不含
分頁類型與主要記憶體指標的對應關係顯示 Private 常駐分頁、Private 非常駐分頁、共用常駐分頁、僅保留範圍分別被哪些指標涵蓋Private‧已認可‧RAM 常駐Private‧已認可‧RAM 未常駐共用分頁‧RAM 常駐Reserved‧未認可Working SetPrivate Working SetPrivate BytesVirtual Bytes 系

圖3:Working Set 與 Private Bytes 計算的是不同的分頁集合,因此不是單純的包含關係。

這裡最重要的一點是:Working Set 與 Private Bytes 並非單純的包含關係

Private Bytes 之中,包含了雖屬處理程序專屬、但目前並未常駐於 RAM 的分頁。而 Working Set 中,則包含 DLL 程式碼、共用記憶體等 Private Bytes 不會計入的共用分頁。因此,依處理程序與時間點不同,Working Set 有可能大於 Private Bytes,反過來也有可能。

此外,若單純把多個處理程序的 Working Set 加總,共用 DLL 等同一塊實體分頁有可能被重複計算多次。「各處理程序 Working Set 的總和 = 使用中的 RAM」這個等式並不一定成立。

3. 虛擬位址空間 ── Reserve 與 Commit 是不同的東西

3.1. 虛擬位址不是實體 RAM 的門牌號碼

每個處理程序都擁有專屬於自己的虛擬位址空間。應用程式操作的指標並不是直接指向實體 RAM 的位置,而是由 Windows 透過分頁表(page table),把虛擬位址對應到實體分頁或檔案上的資料。7

因此,即使電腦搭載了 64GB RAM,某個 32 位元處理程序能使用的虛擬位址空間通常仍遠比這個數字小得多,這種情況是有可能發生的。反過來,虛擬位址空間比實體 RAM 更大的 64 位元處理程序,也是很常見的。

3.2. Reserved 只是「先佔下位址」

VirtualAllocMEM_RESERVE,會為未來使用而先保留一段連續的虛擬位址範圍。在這個階段,並沒有任何實體儲存體與該分頁建立關聯,也無法對這段範圍進行讀寫。45

例如,即使資料庫或執行環境為了未來的成長而 Reserve 了 8GB 的位址範圍,光是這樣並不會因此消耗 8GB 的 RAM 或 8GB 的 Private Bytes。

3.3. Committed 是「需要時會提供支援」的承諾狀態

MEM_COMMIT 是一項操作,會把該虛擬分頁變成 Committed 狀態,並讓 Windows 承諾會提供必要的後盾支援。至於實際上是否允許讀取、寫入、執行,則是由 PAGE_READONLYPAGE_READWRITEPAGE_EXECUTEPAGE_NOACCESS 等分頁保護屬性另外決定的,已認可(Committed)本身並不代表「可讀寫」。在完成 Commit 的當下,就會計入系統的 Commit Charge,但實際的實體分頁有可能要等到第一次存取時才會被指派。第一次觸及的分頁會被清零初始化,經過 Demand-zero fault 之後才會進入 Working Set。51

因此,即使同樣稱作「已配置」,實際上分成以下3個階段。

從 Reserve 到 Commit 再到 RAM 常駐的3個階段顯示保留虛擬位址、認可分頁、第一次存取時指派實體分頁並進入 Working Set 的流程MEM_COMMIT第一次存取‧Demand-zero fault尚未存取則MEM_RESERVE:保留位址範圍反映在 Virtual Bytes 系已認可:依分頁保護屬性可存取反映在 Private Bytes / System Commit指派實體分頁並常駐於 RAM反映在 Working Set已認可但未常駐

圖4:Reserve、Commit、第一次存取是各自獨立的事件,會分別影響不同的指標。

這3個階段,分別會各自撼動 Virtual Bytes 系、Private Bytes、Working Set 這3組數字。

3.4. 為何明明還有空閒 RAM 卻會發生 OutOfMemory

記憶體配置能否成功,並非只由空閒 RAM 決定。

  • 處理程序的虛擬位址空間已經用完
  • 沒有所需大小的連續空閒位址範圍
  • 系統整體的 Commit Charge 已達到 Commit Limit
  • Job Object、容器、執行環境、程式庫本身有各自的上限
  • 該處理程序是 32 位元處理程序
  • 原生堆積(heap)發生了斷片化

即使在 64 位元 Windows 上,32 位元處理程序的使用者模式虛擬位址空間,若未啟用 IMAGE_FILE_LARGE_ADDRESS_AWARE,通常也只有 2GB。已啟用該旗標的 32 位元應用程式,在 64 位元 Windows 上最多可以使用到 4GB。8

因此,「電腦明明還有 20GB 空閒 RAM,32 位元應用程式卻在使用量接近 1.6GB 時就失敗」這種情況並不矛盾。問題未必出在 RAM,而可能是位址空間的斷片化或碰到了上限。

4. Working Set ── 目前載入 RAM 的分頁

Working Set 是處理程序虛擬位址空間中,目前常駐於實體 RAM 的分頁集合。1

其中混雜了以下各種分頁。

  • 處理程序專屬的堆積與堆疊(heap、stack)
  • EXE 與 DLL 的程式碼、唯讀資料
  • 記憶體對應檔案
  • 共用記憶體
  • 經過寫入時複製(copy-on-write)之後變成該處理程序專屬的分頁
  • 執行環境或各種程式庫曾經觸及過的分頁

4.1. Working Set 增加不代表配置量增加

若對一個已經 Committed 的分頁第一次進行存取,就有可能出現 Private Bytes 不變、只有 Working Set 增加的情況。把一個大檔案做記憶體對應並依序讀取時,也可能只是來自檔案的分頁進入 Working Set,Private Bytes 幾乎不增加。

反過來,當 Windows 因應記憶體壓力而 Trim 掉 Working Set 時,即使應用程式邏輯上持有的記憶體不變,也只有 Working Set 會減少。之後若再次觸及該分頁,就會經過分頁錯誤而重新回到 Working Set。

因此,Working Set 下降不一定代表「應用程式已釋放」,上升也不一定代表「應用程式重新配置了記憶體」。

僅 Working Set 增減的典型流程同一批已認可分頁在第一次存取時進入 RAM,因 Trim 而變成非常駐,重新存取後再度回到常駐,期間 Private Bytes 持續被計入第一次存取因記憶體壓力而 Trim重新存取觸發 Page Fault同一批已認可分頁常駐於 RAM非常駐計入 Working Set不計入 Working Set處於已認可狀態期間持續計入 Private Bytes

圖5:Working Set 會隨常駐狀態上下變動,但只要同一批分頁的 Commit 還在,Private Bytes 就不會減少。

4.2. Working Set 中包含共用分頁

假設同一個 DLL 的程式碼分頁被10個處理程序共用,這個分頁可能會出現在每個處理程序的 Working Set 中,但在實體 RAM 上卻可能只存在一份。即使把所有處理程序的 Working Set 加總起來超過了搭載的 RAM 容量,這並不代表立刻出現異常。

若想更接近「目前該處理程序專屬占用的 RAM」,可以查看 Private Working Set。不過這同樣不是「該處理程序配置過的全部記憶體」,而只是目前常駐中的 Private 分頁而已。

4.3. 強制縮小 Working Set 並不能修復記憶體洩漏

使用 EmptyWorkingSetSetProcessWorkingSetSize,可以把分頁從處理程序的 Working Set 中趕出去。然而,這並不是釋放 Commit 或堆積中參照的操作。有可能出現 Private Bytes 不變、表面上的 RAM 使用量卻下降,而下一次存取時分頁錯誤反而增加的情況。9

如果只是按下「釋放記憶體」按鈕之後,工作管理員的數字暫時變小,一旦重新開始操作又立刻回到原本的水準,那有可能不是「釋放」,而只是 Trim 了 Working Set 而已。

5. Private Bytes ── 處理程序專屬的 Commit 量

Private Bytes 是專屬於該處理程序的已認可虛擬記憶體量。它代表無法與其他處理程序共用的 Commit Charge,不論目前是否常駐於 RAM。在 Microsoft 的 PROCESS_MEMORY_COUNTERS_EX 中,對應這個值的是 PrivateUsage102

Win32 API 中還有一個容易混淆的欄位名稱叫 PagefileUsage,但目前的官方文件把它定義為「該處理程序的 Commit Charge」,並說明它與 PrivateUsage 是同一個值。也就是說,Private Bytes 2GB 並不代表「已經有 2GB 寫入了 pagefile.sys」。2

以下是影響 Private Bytes 的代表性項目。

  • HeapAllocmallocnew 等使用的原生堆積的 Commit
  • VirtualAlloc 直接認可的 Private Data
  • .NET GC 堆積中已認可的區域
  • 執行緒堆疊中實際已認可的部分
  • Copy-on-write 檢視(FILE_MAP_COPY)在建立對應時,為整個檢視配置的 Commit Charge
  • 程式庫或裝置 SDK 內部保留的 Private 緩衝區

FILE_MAP_COPY 建立的 Copy-on-write 檢視,因為每個分頁未來都可能變成 Private,所以 Windows 在建立對應的當下,就會先為整個檢視確保可由分頁檔支撐的 Commit Charge。因此,即使實際上還沒有寫入、還沒有產生 Private 副本,System Commit 與該處理程序的 Commit Charge(Private Bytes)也可能已經增加了整個檢視的份量。11

5.1. 為什麼 free 或 GC 之後 Private Bytes 仍然不會下降

即使應用程式邏輯上「釋放」了記憶體,執行環境或堆積配置器(heap allocator)也可能不把這段區域 Decommit 還給作業系統,而是保留下來供未來重複使用。在這種情況下,即使應用程式內部可以重複使用,Private Bytes 仍會維持在高水位。

此外,若大型區域中只剩一部分還存活、發生了斷片化、快取或連線池已經預熱到上限,也同樣會造成高水位居高不下的現象。

因此,光憑 Private Bytes 偏高並不能證明存在記憶體洩漏。應該檢視的是以下這種時間差比較:

  1. 用完全相同的次數重複執行同一段處理
  2. 每次處理後留出相同的等待時間
  3. 確認 Private Bytes 是否回到相同水準,或是否收斂在某個固定值
  4. 用 VMMap 或堆積傾印(heap dump)確認是哪個區域、哪種型別增加了
為何 free 或 GC 之後 Private Bytes 仍不下降顯示配置器把應用程式已不需要的區域還給作業系統,與保留下來供重複使用兩種情況下 Private Bytes 變化的差異Decommit / Release保留供重複使用應用程式透過 free / GC 釋出區域配置器是否還給作業系統Commit Charge 減少Private Bytes 下降該區域維持已認可狀態Private Bytes 維持在高水位連線池‧快取‧斷片化

圖6:應用程式內部變得可重複使用,與把 Commit 還給作業系統,是兩件不同的事。

5.2. 較強的洩漏候選特徵

以下這種每次負載之後底部都往上墊高的「階梯狀」增長,需要特別留意。

Private Bytes
  ^
  |                    ________
  |             ______|
  |      ______|
  |_____|
  +----------------------------> 重複相同處理

不過,即使是階梯狀,也有可能只是第一次的 JIT、字型、影像解碼器、連線池、快取的預熱過程增加了幾次,之後就穩定下來。比起是否有增加,更重要的是是否無法收斂到穩定狀態

6. System Commit ── 「已認可 X/Y」的真面目

工作管理員的[效能]→[記憶體]中顯示的「已認可 X/Y」,是系統整體的指標。

  • X:System Commit Charge 目前 Windows 對系統整體承諾會支撐的已認可記憶體量
  • Y:System Commit Limit 系統所能支撐的 Commit 量上限

Commit Limit 大致由實體 RAM 與所有分頁檔的總和決定。若沒有分頁檔,則會略小於搭載的 RAM 容量。36

System Commit Charge 與 Commit Limit 的關係各處理程序專屬、共用區段、核心的 Commit 構成目前值 X,實體 RAM 與分頁檔支撐上限 YX 不能超過 Y各處理程序的 Private CommitSystem Commit Charge:X以分頁檔為後盾的共用區段 Commit核心的 Commit實體 RAMSystem Commit Limit:Y分頁檔

圖7:X 是目前的承諾量,Y 是能夠支撐該承諾的上限,並不是分頁檔使用量的顯示。

System Commit Charge 不只包含各處理程序 Private Bytes 的總和,還包含以分頁檔為後盾的共用區段 Commit,以及核心所消耗的 Commit。因此,光靠各處理程序的 Private Bytes 加總,並無法完全解釋 X 的來源。

6.1. Commit Charge 不是分頁檔使用量

假設一台系統擁有 16GB RAM、16GB 分頁檔,已認可量為 20/31GB。

這裡的 20GB 並不代表「已經寫入了 20GB 到分頁檔」。這個 20GB,是指對於 Private 且可修改的分頁等內容,Windows 承諾在需要時會準備好由 RAM 或分頁檔等提供支撐的總量。

在那個當下,實際上是以下這幾種狀態混雜在一起:

  • 多數常駐於 RAM
  • 一部分已退避到分頁檔
  • 已認可但尚未第一次被存取
  • 被消耗在核心端的 Commit 上

若想查看分頁檔的實際使用率,應該與 Commit 分開,另外確認 Paging File(*)\% Usage。不過即使是 Microsoft 的資料也指出,光憑分頁檔使用率偏高並不一定代表有效能問題,還要搭配 Commit Limit 是否逼近、Modified Page List、實際的分頁 I/O 一起判斷。6

6.2. 逼近 Commit Limit 時會發生什麼

當 System Commit Charge 到達 Commit Limit 時,就無法再支撐新的 Commit 需求,可能導致處理程序記憶體配置失敗、應用程式當機、系統無法操作等狀況。3

在這種情況下,比起「空閒 RAM」,Commit 的 X/Y 更加重要。即使 Trim Working Set 騰出了空閒 RAM,只要 Commit Charge 本身不減少,逼近 Commit Limit 的問題就不會解決。

6.3. 分頁檔的3個角色

分頁檔主要有以下角色。

  1. 擴大 Commit Limit
  2. 讓使用頻率低的已修改分頁能夠從 RAM 退避出去
  3. 依組態支援系統當機傾印檔(crash dump)

停用分頁檔並不是「磁碟 I/O 一定會減少、系統一定會變快」這種單純的關係。反而可能降低 Commit Limit,使已修改但暫時用不到的分頁更容易滯留在 RAM 中,並且在當機時無法取得所需的傾印檔。36

分頁檔的適當大小,並不是單靠搭載的 RAM 容量就能決定。Microsoft 也說明,由於尖峰時期的 System Commit Charge 以及所需的當機傾印類型因系統而異,無法一概而論。6

7. 實體 RAM 的組成 ── 不能只憑 Available 偏低就下判斷

實體 RAM 並非只用於使用者處理程序的 Working Set。

  • 各處理程序的 Working Set
  • 系統檔案快取
  • Standby、Modified、Free、Zeroed 等各種分頁清單
  • 核心的 Paged Pool / Nonpaged Pool
  • 裝置驅動程式保留的記憶體
  • 記憶體壓縮儲存區
  • 與 GPU 或裝置共用、保留的區域
  • 硬體保留區

7.1. Available 也包含可重複使用的快取

Windows 的 Available MBytes,並不單純只是完全未使用的 RAM。它是一個包含 Free、Zeroed,再加上必要時可重複使用的 Standby 分頁在內的指標。12

  • Free:目前尚未指派給任何用途的分頁
  • Zeroed:已清零、可安全交給其他處理程序使用的分頁
  • Standby:已離開 Working Set,但內容仍快取在 RAM 上的分頁
  • Modified:內容已被修改、在重複使用前需要先寫回適當後盾媒介的分頁
Working Set 與各分頁清單之間的移動顯示使用中分頁在未修改時移往 Standby、已修改時移往 Modified,再經由重新存取、寫回、重複使用等流程移出未修改分頁移出已修改分頁完成寫回重新存取重複用於其他用途清零配置後被存取Working Set:使用中Standby:內容仍保留的候選重複使用分頁Modified:等待寫回指派給其他用途Free:未使用Zeroed:可供新配置計入 Available

圖8:Available 不只包含完全空閒的部分,也包含必要時可重複使用的 Standby。

「為了增加空閒 RAM 而把快取全部丟棄」,並不總是划算的做法。若 Standby 上還保留著所需的資料,重新存取時就能不讀取磁碟,快速回到 Working Set。

因此,即使工作管理員顯示 Free 偏低,只要 Available 還算充足,也沒有出現硬性分頁錯誤或磁碟等待造成的問題,那有可能只是 Windows 正把 RAM 有效當作快取來使用而已。

7.2. 沒有大型處理程序卻 RAM 減少的情況

即使把各處理程序的 Private Working Set 全部加總,也無法解釋清楚的記憶體消耗並不罕見。

  • 檔案快取或記憶體對應檔案
  • Nonpaged Pool / Paged Pool
  • 驅動程式鎖定的分頁
  • 共用分頁
  • 記憶體壓縮
  • 虛擬化或 GPU 相關的配置

在這種情況下,與其一直盯著處理程序清單看,不如用 Sysinternals 的 RAMMap 確認 Use Counts、Processes、Priority Summary、File Summary。RAMMap 是一個官方工具,可以依用途、分頁清單、檔案把實體記憶體分解開來檢視。13

如果只有 Nonpaged Pool 持續增加,這時應該懷疑的就不是使用者模式應用程式的 Private Bytes,而是要開始懷疑驅動程式或核心端是否存在洩漏。

8. Page Fault ── 數量多不代表異常

當處理程序存取一個目前不在 Working Set 中的分頁時,就會發生 Page Fault。雖然名稱中有「Fault」(錯誤)這個字,但它並不是例外性的故障,而是驅動虛擬記憶體運作的正常機制之一。1

8.1. 軟性分頁錯誤(Soft Page Fault)

不需要讀取磁碟就能解決的分頁錯誤。

  • 分頁仍留在 Standby 或 Transition 狀態
  • 其他處理程序的 Working Set 中有相同的共用分頁
  • 第一次存取已認可分頁,需要指派一個清零分頁
  • 記憶體管理員已經透過預先讀取(read-ahead)把資料載入 RAM 中

因此,即使 \Memory\Page Faults/sec 數值很高,也不一定代表發生了磁碟 I/O 或延遲。

8.2. 硬性分頁錯誤(Hard Page Fault)

需要從磁碟上的後盾儲存體讀取內容的分頁錯誤。讀取來源不限於分頁檔。

  • .exe.dll 的程式碼、資料
  • 記憶體對應檔案
  • 分頁檔
軟性分頁錯誤與硬性分頁錯誤的分支存取不在 Working Set 中的分頁時,若不需要儲存體 I/O 就處理為軟性分頁錯誤,需要時則處理為硬性分頁錯誤否:Standby‧共用‧Demand-zero 等存取不在 Working Set 中的分頁是否需要儲存體 I/O軟性分頁錯誤不讀取磁碟直接進入 Working Set硬性分頁錯誤從哪裡讀取EXE / DLL記憶體對應檔案分頁檔讀取後進入 Working Set

圖9:光憑 Page Fault 這個名稱,無法判斷是否伴隨磁碟 I/O。

Microsoft 列出了用來量測硬性分頁錯誤的計數器,包括 \Memory\Pages/sec\Memory\Page Reads/sec\Memory\Pages Input/sec 等。這些數值即使偏高,也不一定代表記憶體不足,因此需要與 Available MBytes、磁碟延遲、實際的回應時間相互對照。6

8.3. 不要設定一律通用的門檻值

像「Page Faults/sec 超過 1000 就算異常」這種固定數值,其意義會依儲存體、分頁大小、工作負載、存取局部性而改變。

實務上,建議把以下這些指標放在同一個時間軸上比對:

  • Memory\Available MBytes
  • Memory\Pages Input/sec
  • Memory\Page Reads/sec
  • 對象磁碟的讀取延遲/佇列
  • 對象處理程序的 Working Set、Private Bytes
  • 應用程式的處理時間、逾時、UI 回應

如果負載增加的同時 Available 也下降,Pages Input/sec 與磁碟等待上升,處理時間也隨之惡化,那就有充分理由懷疑是實體記憶體壓力所導致的分頁作業。

9. 該用哪個畫面、工具看什麼

想知道的事 首先要看的指標 主要工具
目前對象處理程序載入 RAM 的量 Working Set 工作管理員、Process Explorer、Get-Process
其中屬於處理程序專屬的 RAM Private Working Set / Working Set - Private 工作管理員的詳細資料欄位、Process Explorer、PerfMon
對象處理程序專屬的 Commit 量 Private Bytes / Commit Size Process Explorer、PerfMon、VMMap、Get-Process
處理程序的虛擬位址範圍 Virtual Bytes / Size Process Explorer、VMMap、Get-Process
系統整體的 Commit 餘裕 Committed Bytes / Commit Limit 工作管理員[效能]、PerfMon
實體 RAM 的可重複使用餘裕 Available MBytes 工作管理員、PerfMon
Standby、Modified、檔案快取的組成 依分頁清單、用途分類 RAMMap
Private Bytes 中是什麼增加了 Heap / Private Data / Managed Heap 等 VMMap、WinDbg、各執行環境專用傾印
伴隨磁碟的分頁作業 Pages Input/sec、Page Reads/sec、磁碟延遲 PerfMon、WPR/WPA
選擇 Windows 記憶體調查工具依對象是單一處理程序或系統整體、是單一時間點或時序資料、是否需要追到執行環境內部,來選擇對應的工具單一時間點的組成時序資料系統整體實體 RAM 的組成含 CPU‧I/O‧等待的時間軸.NET 堆積原生堆積想釐清的是什麼對象是單一處理程序嗎單一時間點還是時序資料VMMapPerfMon / PowerShell要看實體 RAM 的組成還是時間軸RAMMapWPR / WPA是否要追到執行環境內部的持有狀況dotnet-dump / PerfViewWinDbg / Application Verifier

圖10:先決定對象範圍與時間軸,就能不多不少地選出所需要的工具。

9.1. 工作管理員

在工作管理員中,要分畫面來看。

  • [處理程序]或[詳細資料]:個別處理程序的 Working Set 系、Commit Size 系
  • [效能]→[記憶體]:系統整體的 In use、Available、Committed、Cached、Paged pool、Non-paged pool

不要只憑「記憶體」這個欄位名稱就下判斷,請在[詳細資料]頁籤右鍵點擊欄位標題,加入 Working Set、Peak Working Set、Commit Size 等所需的欄位。由於 Windows 的版本與顯示語言不同,欄位名稱可能略有差異,請先確認欄位的意義再記錄

9.2. 用 PowerShell 取得時序資料

如果已經知道對象處理程序的 ID,就可以用 Get-Process 同時取得 Working Set、Private Bytes、Virtual Bytes 的變化趨勢。

param(
    [Parameter(Mandatory)]
    [int]$ProcessId,

    [int]$IntervalSeconds = 5,
    [int]$SampleCount = 60
)

$samples = for ($i = 0; $i -lt $SampleCount; $i++) {
    $process = Get-Process -Id $ProcessId -ErrorAction Stop

    [pscustomobject]@{
        Timestamp      = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        ProcessId      = $process.Id
        WorkingSetMB   = [math]::Round($process.WorkingSet64 / 1MB, 1)
        PrivateBytesMB = [math]::Round($process.PrivateMemorySize64 / 1MB, 1)
        VirtualBytesMB = [math]::Round($process.VirtualMemorySize64 / 1MB, 1)
        Handles        = $process.HandleCount
        Threads        = $process.Threads.Count
    }

    Start-Sleep -Seconds $IntervalSeconds
}

$samples | Format-Table -AutoSize
$samples | Export-Csv .\memory-samples.csv -NoTypeInformation -Encoding utf8

.NETProcess.WorkingSet64 對應 Working Set,PrivateMemorySize64 對應 Private Bytes,VirtualMemorySize64 對應 Virtual Bytes。141516

若應用程式有多個執行個體,請用 PID 而不是名稱來追蹤。若是重啟後 PID 會改變的長期監控,需要記錄啟動時間或服務名稱等資訊,設計上避免追錯對象。

9.3. 用 PerfMon 把系統與處理程序放在同一個時間軸

至少同時記錄以下項目,會比較容易做切分。

\Process(<對象>)\ID Process
\Process(<對象>)\Working Set
\Process(<對象>)\Working Set - Private
\Process(<對象>)\Private Bytes
\Process(<對象>)\Virtual Bytes

\Memory\Available MBytes
\Memory\Committed Bytes
\Memory\Commit Limit
\Memory\Pages Input/sec
\Memory\Page Reads/sec
\Memory\Pool Nonpaged Bytes
\Memory\Pool Paged Bytes

當存在多個同名處理程序,或是監控期間發生重啟時,光靠 Process(name)Process(name#N) 這種執行個體名稱,無法固定對象。請在每次取樣時也記錄 ID Process,只採用該值與追蹤對象 PID 一致的執行個體。若橫跨了 PID 會改變的重啟,也要另外記錄切換發生的時間點。

Windows 的效能計數器名稱可能會依顯示語言而本地化。若直接以 PowerShell 指定英文名稱卻找不到,可以透過 PerfMon 的圖形介面加入計數器,或是用 Get-Counter -ListSet * 確認本機環境中的名稱。

9.4. 不要混淆 VMMap 與 RAMMap 的角色

  • VMMap:把單一處理程序的虛擬記憶體與 Working Set,分解成 Heap、Image、Mapped File、Private Data、Managed Heap 等類別
  • RAMMap:把系統整體的實體 RAM,依用途、分頁清單、處理程序、檔案分解開來

「這個處理程序的 Private Bytes 是什麼原因增加的」用 VMMap;「處理程序清單無法解釋的 RAM 用在哪裡了」用 RAMMap。1713

10. 從數字組合判讀症狀

觀察到的形態 首先該考慮的原因 接下來要確認的項目
Working Set 增加,Private Bytes 穩定 對既有分頁的第一次存取、共用 DLL、對應檔案、檔案快取 VMMap 的 Image / Mapped File、Pages Input/sec
Private Bytes 增加,Working Set 穩定 Private 的 Commit 增加了,但處於未常駐或已被 Trim 的狀態 VMMap 的 Heap / Private Data / Managed Heap
兩者都在啟動後立即增加,之後持平 JIT、快取、連線池、初始化的預熱 追加相同負載後是否會再次增加
每次負載後 Private Bytes 的底部都往上墊高 洩漏、無上限的快取、釋放後仍保留的配置器 前後的 VMMap 快照、堆積傾印
只有 Working Set 突然下降,操作後又回升 作業系統或應用程式 Trim 了 Working Set Private Bytes、Pages Input/sec、回應時間
已認可 X/Y 的 X 逼近 Y 系統整體的 Commit 壓力 Private Bytes 前幾名、Paged/Nonpaged Pool、分頁檔設定
Available 偏低,Pages Input/sec 與磁碟延遲偏高 實體 RAM 壓力與硬性分頁作業 Working Set 前幾名、RAMMap、與工作負載的相關性
RAM 使用率高但沒有大型處理程序 快取、共用分頁、核心集區、驅動程式、壓縮等 RAMMap、Pool Nonpaged/Paged Bytes
明明有空閒 RAM,只有 32 位元應用程式失敗 虛擬位址空間上限、斷片化 VMMap 的 Free/Reserved、執行檔的 LAA 設定
Private Bytes 偏高,但重複處理也不會再增加 有可能是維持高水位的連線池或快取 上限、重複使用狀況、尖峰後是否穩定

這張表中最重要的是:不要只看單一數值,而要看組合

11. 記憶體洩漏調查的實務步驟

11.1. 先決定重現條件與穩定基準點

光說「跑幾天就會增加」是無法比較的。

需要先決定:

  • 啟動後的預熱要涵蓋到什麼程度
  • 一個循環要包含哪些操作
  • 一個循環結束後要等待幾秒
  • 需要重複幾次才能達到快取上限
  • 正常版本與有問題的版本是否能使用相同的輸入

11.2. 同時記錄處理程序與系統

至少要在同一時間點留下以下記錄:

  • 對象的 Working Set
  • 對象的 Private Bytes
  • 對象的 Virtual Bytes
  • 系統的 Committed Bytes / Commit Limit
  • Available MBytes
  • Pages Input/sec
  • 控制代碼(handle)數量、執行緒數量
  • 操作次數或處理件數

如果對象處理程序的 Private Bytes 很穩定,但系統的 Commit 卻持續增加,就需要把視野擴大到其他處理程序、核心、驅動程式、共用區段等範圍。

11.3. 先決定正在增加的是哪個「維度」

  • 只有 Working Set:常駐分頁、來自共用或檔案、Trim 與重新載入
  • Private Bytes:處理程序專屬的 Commit
  • 只有 Virtual Bytes:Reserve、對應、位址空間斷片化
  • 只有 System Commit:也包含其他處理程序或核心端
  • Nonpaged Pool:驅動程式、核心端
  • Handles / GDI / USER:記憶體以外的資源洩漏

若跳過這個順序直接取傾印檔,就會在對象錯誤的情況下讀取大量資訊。

11.4. 進一步深入細項

  • 原生處理程序:VMMap、WinDbg、Application Verifier、堆積追蹤
  • .NET:dotnet-countersdotnet-gcdumpdotnet-dump、PerfView
  • 系統整體:RAMMap、PerfMon、WPR/WPA
  • 核心集區:PoolMon、WinDbg

VMMap 會依類別顯示處理程序已認可的虛擬記憶體,以及各自分配到的 Working Set。能把 Private Bytes 的增加縮小到「Heap」「Private Data」「Managed Heap」「Mapped File」中的哪一項,會大幅影響後續調查的成本。17

11.5. 修正後要在相同條件下比較變化趨勢

修正前後只比較尖峰值是不夠的。如果初始值不同,很容易發生逆轉的情況。

在以下條件相同的前提下:

  • 相同的啟動狀態
  • 相同的輸入
  • 相同的操作次數
  • 相同的等待時間
  • 相同的取樣間隔

比較每個循環結束後的底值與變化趨勢。修正記憶體洩漏的證明,不是「最大值變小了」,而是即使重複相同的負載,增長也會收斂

12. 重新表述常見的誤解

誤解1:工作管理員的記憶體=應用程式配置的全部量

重新表述:要先確認是哪一個欄位。若是 Working Set 系,代表目前常駐於 RAM 的量;若是 Commit Size 系,代表處理程序專屬的 Commit 量。

誤解2:Private Bytes=分頁檔上的位元組數

重新表述:Private Bytes 是 Private 的 Commit Charge。它是一個邏輯上的承諾量,其中既包含目前在 RAM 中的分頁,也包含需要時會由分頁檔支撐的分頁。

誤解3:已認可 X/Y=分頁檔使用量/分頁檔容量

重新表述:X 是系統整體的 Commit Charge,Y 是 Commit Limit。分頁檔會擴大 Y,但 X 並不會直接等於磁碟上的實際使用量。

誤解4:Page Faults/sec 偏高=正在對磁碟做交換(swap)

重新表述:其中也包含軟性錯誤。是否伴隨磁碟 I/O,要用 Pages Input/sec、Page Reads/sec 與磁碟延遲來確認。

誤解5:空閒 RAM 偏少=記憶體不足

重新表述:要看 Available、Standby、硬性分頁作業、回應時間。用可重複使用的快取把 RAM 填滿是正常現象。

誤解6:Working Set 縮小了=修好了記憶體洩漏

重新表述:有可能只是把分頁從 RAM 中趕出去而已。要確認 Private Bytes 或堆積中持有的量是否真的減少了。

誤解7:Private Bytes 增加了=確定是洩漏

重新表述:只有在確認了重複相同工作負載後是否會收斂、增加的是哪一種類型的記憶體、是否屬於可釋放的快取之後,才能做出判斷。

13. 總結

  • Windows 的「記憶體使用量」並不是單一數字。要分開思考位址空間、Commit、RAM 常駐、可共用性。
  • Working Set 是目前在 RAM 中的分頁,同時包含 Private 與 Shared 兩者。Private Working Set 則是其中屬於處理程序專屬的常駐分頁。
  • Private Bytes 是處理程序專屬的 Commit Charge,既不是目前在 RAM 中的量,也不是實際寫入分頁檔的量。
  • 已認可 X/Y 是系統整體的 Commit Charge / Commit Limit。分頁檔主要用來支撐 Commit Limit、已修改分頁的退避、以及當機傾印。
  • Reserve 的虛擬位址、Commit 的分頁、實際觸及後進入 Working Set 的分頁,是不同的階段。
  • Page Fault 是正常動作,軟性錯誤不會讀取磁碟。硬性錯誤除了分頁檔之外,也會由 EXE、DLL、對應檔案觸發。
  • 記憶體洩漏不是靠單一時間點的大小來判斷,而是靠相同負載後的底值與變化趨勢,以及細項組成來證明。
  • 個別處理程序的細項用 VMMap,系統整體的實體 RAM 用 RAMMap,時序資料用 PerfMon,執行環境內部則要交給專用的傾印工具。

下次在工作管理員中發現「記憶體在增加」時,請先這樣重新提問:

增加的到底是 Working Set、Private Bytes、Virtual Bytes,還是 System Commit?

光是這個問題,就能讓調查的切入點準確許多。

相關文章

相關諮詢領域

合同會社小村軟體提供 Windows 應用程式記憶體增長、長時間運轉後效能劣化、32 位元處理程序的 OutOfMemory、僅在客戶環境中發生的記憶體不足等問題的原因調查,結合 PerfMon、VMMap、RAMMap、WinDbg 與 .NET 診斷工具進行分析。我們不會只停留在「記憶體很多」這種結論,而是會切分到究竟是哪個區域、透過哪個操作、為何增加,以及是從何處被參照、持有的。

參考連結

  1. Microsoft Learn,Working Set。關於處理程序的 Working Set 是目前常駐於實體記憶體的分頁集合、其中包含共用分頁、軟性與硬性分頁錯誤的差異、Transition 分頁、以及從 Working Set 移除分頁等內容。  2 3 4 5

  2. Microsoft Learn,PROCESS_MEMORY_COUNTERS_EX2 structure。關於 WorkingSetSize、PrivateWorkingSetSize、PrivateUsage、SharedCommitUsage 的定義,以及 PagefileUsage 與 PrivateUsage 皆代表處理程序 Commit Charge 這一點。  2 3 4

  3. Microsoft Learn,Introduction to page files。關於分頁檔支撐已修改分頁的退避、系統當機傾印檔、擴大 System Commit Limit,以及 System Commit Charge 與 Commit Limit 的定義,還有在工作管理員與效能計數器中的量測方式。  2 3 4

  4. Microsoft Learn,Page State。關於虛擬分頁的 Free、Reserved、Committed 各種狀態,以及 Reserved 分頁尚未關聯任何實體儲存體、也無法存取這一點。  2

  5. Microsoft Learn,VirtualAlloc function。關於 MEM_RESERVE 與 MEM_COMMIT 的差異、Commit 時會從系統整體記憶體與分頁檔中扣除 Charge、以及實際的實體分頁有可能要等到第一次存取時才會被指派。  2 3

  6. Microsoft Learn,How to determine the appropriate page file size for 64-bit versions of Windows。關於分頁檔的大小取決於尖峰 Commit Charge 與當機傾印需求、硬性分頁錯誤的讀取來源不限於分頁檔而也包含 EXE、DLL、記憶體對應檔案,以及相關的效能計數器。  2 3 4 5 6

  7. Microsoft Learn,Virtual Address Space。關於每個處理程序擁有獨立的虛擬位址空間與分頁表,以及虛擬位址本身並非實體位址這一點。 

  8. Microsoft Learn,Memory Limits for Windows and Windows Server Releases。關於 32 位元處理程序的使用者模式虛擬位址空間通常為 2GB,以及在 64 位元 Windows 上會依 IMAGE_FILE_LARGE_ADDRESS_AWARE 的有無而變為 2GB 或 4GB。 

  9. Microsoft Learn,SetProcessWorkingSetSize function。關於 Working Set 的最小值與最大值並不保證常駐、可以把 Working Set 清空,以及過度設定或操作可能使系統效能惡化。 

  10. Microsoft Learn,Memory Performance Information。關於 Windows 效能計數器、記憶體管理 API、工作管理員顯示三者之間的對應關係,以及 Process 的 Working Set、Working Set - Private、Private Bytes,System 的 Committed Bytes、Commit Limit。 

  11. Microsoft Learn,MapViewOfFile function。關於 FILE_MAP_COPY 中每個分頁都可能變成 Copy-on-write,因此在建立對應的當下,就會為了讓分頁檔能支撐整個檢視而先確保 Commit Charge。 

  12. Microsoft Learn,Understanding Node Metrics and Properties in HPC Cluster Manager。關於 Available Physical Memory 是由 Zeroed、Free、Standby 各清單加總計算而成,以及各分頁清單的意義。 

  13. Microsoft Sysinternals,RAMMap。關於分析 Windows 實體記憶體使用量的功能,可依用途、分頁清單、處理程序、優先順序、實體分頁、檔案等單位進行分析。  2

  14. Microsoft Learn,Process.WorkingSet64 Property。關於 WorkingSet64 以位元組為單位傳回處理程序的 Working Set,並對應 Process 的 Working Set 效能計數器。 

  15. Microsoft Learn,Process.PrivateMemorySize64 Property。關於 PrivateMemorySize64 傳回無法與其他處理程序共用的處理程序專屬記憶體,並對應 Private Bytes 效能計數器。 

  16. Microsoft Learn,Process.VirtualMemorySize64 Property。關於 VirtualMemorySize64 傳回處理程序的虛擬記憶體量,並對應 Virtual Bytes 效能計數器。 

  17. Microsoft Sysinternals,VMMap。關於將處理程序已認可的虛擬記憶體依類別分解,並顯示各自分配到的實體記憶體(Working Set)與詳細記憶體對應表的功能。  2

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

工作管理員的「記憶體」是應用程式所配置的全部記憶體嗎?
不是。工作管理員有 Working Set 系、Private Working Set 系、Commit Size 等多個記憶體欄位,看哪個畫面、哪個欄位,意義就不一樣。Working Set 是目前載入 RAM 的分頁,Private Bytes 或 Commit Size 則是該處理程序專屬的 Commit 量。請不要把單一「記憶體」欄位直接解讀成應用程式配置的總容量或洩漏量。
Working Set 和 Private Bytes 有什麼不同?
Working Set 是該處理程序可見、目前常駐於實體 RAM 的分頁量,其中也包含 DLL、記憶體對應檔案等可共用的分頁。Private Bytes 則是僅該處理程序專用的已認可(committed)記憶體量,不論目前是否常駐於 RAM。因此兩者不會相等,也不存在單純的大小關係。
工作管理員的「已認可 18/32GB」,是指已經有 18GB 寫入分頁檔了嗎?
不是。左邊是系統整體目前承諾的 Commit 量,右邊是系統能夠支撐的 Commit 上限。上限大致由 RAM 與分頁檔的總和決定,但左邊的總量並非全部都在分頁檔上。多數已認可的分頁其實在 RAM 中,也有一些分頁從未真正被指派過實體分頁。另一方面,像 EXE、DLL、記憶體對應檔案這類可以從原始檔案重新讀取的分頁,即使增加了 Working Set,也不一定會等量增加 Private 的 Commit。
明明還有空閒 RAM,也會發生 OutOfMemory 嗎?
會。32 位元處理程序的虛擬位址空間不足、連續空閒位址範圍不足、系統的 Commit 上限、Job Object 或執行環境特有的上限等,都是實體 RAM 以外會導致配置失敗的條件。特別是 64 位元 Windows 上的 32 位元處理程序,若沒有標記 Large Address Aware,使用者模式虛擬位址空間通常上限就是 2GB。
停用分頁檔會讓 Windows 變快嗎?
一般而言不能斷定會變快。停用分頁檔會降低系統的 Commit 上限,使未使用的已修改分頁更難從 RAM 中退出,也會影響當機傾印檔(crash dump)的取得方式。分頁檔的大小應該根據尖峰時期的 Commit 量與所需的當機傾印類型來決定,而不是沒有根據就直接停用。
Page Faults/sec 很多就代表記憶體不足嗎?
光靠這一項無法判斷。分頁錯誤(page fault)包含可以用 RAM 內的 Standby 分頁或與其他處理程序共用的分頁解決的軟性錯誤(soft fault),以及需要從磁碟讀取的硬性錯誤(hard fault)。請不要只看 Page Faults/sec,而是把 Pages Input/sec、Page Reads/sec、Available MBytes、磁碟延遲、處理時間放在同一個時間軸上一起確認。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽