מה באמת אומר "שימוש בזיכרון" של Windows — קריאה נכונה של Working Set, Private Bytes, Commit וקובץ הדפדוף
· Go Komura · Windows, פיתוח Windows, ניהול זיכרון, Working Set, Private Bytes, Commit, קובץ דפדוף, ניטור ביצועים, איתור תקלות, Sysinternals
מנהל המשימות מציג את ה”זיכרון” של תהליך כ-1.2GB. ובכל זאת Process Explorer מציג Working Set של 1.5GB ו-Private Bytes של 2.4GB, ו-Size של VMMap גדול עוד יותר. כשמסתכלים על המערכת כולה, כתוב “Committed 19.6/31.8GB”.
אז כמה גיגה-בתים של זיכרון האפליקציה הזו באמת משתמשת, בסופו של דבר?
התשובה היא שאיזה מספר כדאי להסתכל עליו תלוי במה שאתם באמת רוצים לדעת. המדד לשימוש שונה בהתאם לשאלה אם אתם רוצים את הכמות שכרגע מתגוררת ב-RAM, את הכמות שהוקצתה ספציפית לאותו תהליך, את הכמות שהמערכת הבטיחה להמשיך לגבות בעתיד, או פשוט את טווח הכתובות הווירטואליות ששמור.
מה שמבלבל במדדי הזיכרון של Windows הוא שהם כולם מוצגים תחת אותה מילה, “זיכרון”, אף על פי שבפועל הם מודדים את הצירים הנפרדים הבאים.
- כמה מרחב כתובות בשימוש
- כמה Commit נצרך
- האם זה כרגע מתגורר ב-RAM הפיזי
- האם הדף פרטי לתהליך, או ניתן לשיתוף
- כמה הקצאה נוספת המערכת כולה עדיין יכולה לתמוך בה
המאמר מיועד למי שחוקר זיכרון אפליקציה שגדל או מחסור בזיכרון ברמת המערכת ב-Windows 10/11 וב-Windows Server הנוכחי, ומחבר בתמונה אחת את הקשרים בין Working Set, Private Working Set, Private Bytes, Commit, Virtual Bytes, קובץ הדפדוף, Available ותקלות דף.
ההליך לעקוב אחרי הסיבה שאובייקטי .NET אינם נאספים מכוסה בפירוט ב”להבחין בין עיכוב GC לדליפת זיכרון ב-.NET”, וההפעלה המעשית של 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. גם השדה
PagefileUsageבמבנה של Win32 API מייצג, ב-Windows הנוכחי, בפועל את אותו Commit Charge, ואינו מספר הבתים שנכתבו בפועל לקובץ הדפדוף.2 - “Committed X/Y” במנהל המשימות מציג את X כסך ה-Commit הנוכחי של המערכת ואת Y כתקרת ה-Commit. X אינו שימוש בקובץ הדפדוף. Y נקבע בערך לפי RAM ועוד קובץ הדפדוף.3
- Reserve ו-Commit הם דברים שונים. שמירה בלבד של טווח כתובות וירטואלי מניחה את הטווח בצד לשימוש עתידי; היא אינה צורכת את אותה כמות לא של RAM ולא של תקרת ה-Commit.45
- Page Fault אינו בהכרח אומר קלט/פלט דיסק. יש soft fault, שאפשר לפתור בתוך ה-RAM, ו-hard fault שקוראים מקובץ הדפדוף, מקובצי הרצה, מקבצים שממופים לזיכרון וכדומה.16
- דליפת זיכרון נשפטת לא מקריאה אחת אלא מהמגמה כשחוזרים על אותו עומס. בפרט, עקבו אם Private Bytes והפירוט שלו ממשיכים לטפס מדרגה-מדרגה גם אחרי שהעיבוד נגמר, בלי לחזור לאותו מצב יציב.
במשפט אחד: Working Set הוא “הכמות שנמצאת כרגע ב-RAM”, Private Bytes הוא “הכמות שהובטחה ספציפית לתהליך הזה”, ו-Commit הוא “הכמות שהמערכת כולה הבטיחה”.
flowchart TB
accTitle: בחירת מדד הזיכרון הנכון של Windows
accDescr: איזה מדד להסתכל עליו תלוי בשאלה אם רוצים שהייה ב-RAM, Commit פרטי לתהליך, Commit ברמת המערכת, או טווח כתובות וירטואלי
question["מה אתם רוצים לדעת על שימוש בזיכרון"]
question -->|הכמות כרגע ב-RAM| workingSet["Working Set"]
question -->|הכמות המובטחת הפרטית לתהליך| privateBytes["Private Bytes"]
question -->|הכמות המובטחת ברמת המערכת| systemCommit["System Commit"]
question -->|טווח כתובות שמור| virtualBytes["Virtual Bytes / Reserved"]
workingSet --> resident["שהייה ב-RAM הפיזי"]
privateBytes --> privateCommit["Commit פרטי לתהליך"]
systemCommit --> commitLimit["השוואה מול Commit Limit"]
virtualBytes --> addressSpace["מרחב כתובות וירטואלי"]
איור 1: פרקו תחילה את התצפית “הזיכרון גבוה” לארבע שאלות נפרדות.
2. פיצול “שימוש בזיכרון” לארבעה צירים
בתחילה, חשבו על זיכרון Windows לא כעל “פס אחד” אלא לאורך ארבעה צירים.
flowchart TB
accTitle: ארבעה צירים עצמאיים לסיווג דף אחד
accDescr: בדקו בנפרד את מצב הכתובת הווירטואלית, את הגיבוי של דפים committed, את השהייה ב-RAM הפיזי, ואת אפשרות השיתוף עם תהליכים אחרים
page["הסתכלו על דף אחד בארבעה צירים"]
page --> address["מצב הכתובת"]
address --> addressValues["Free / Reserved / Committed"]
page --> backing["גיבוי"]
backing --> backingValues["Page-file-backed / File-backed"]
page --> residentAxis["שהייה ב-RAM"]
residentAxis --> residentValues["Resident / Not resident"]
page --> sharing["אפשרות שיתוף"]
sharing --> sharingValues["Private / Shareable"]
איור 2: גם בדף אחד, מצב הכתובת, הגיבוי, השהייה ואפשרות השיתוף נקבעים כל אחד בנפרד.
Mapped אינו מצב כתובת לצד Free, Reserved ו-Committed — זו קטגוריה של אזור. גם דפים בתצוגה ממופה יכולים להיות Committed. באותו אופן, Private אינו מדיום גיבוי אלא סיווג של אפשרות שיתוף. לכן קראו גיבוי כ-Page-file-backed או File-backed, ואפשרות שיתוף כ-Private או Shareable, בנפרד.
צירוף ארבעת הצירים האלה נותן את הקשר בין המדדים הייצוגיים כך.
| מצב הדף | Working Set | Private Working Set | Private Bytes | משפחת Virtual Bytes |
|---|---|---|---|---|
| פרטי לתהליך, committed, מתגורר ב-RAM | כלול | כלול | כלול | כלול |
| פרטי לתהליך, committed, אינו מתגורר ב-RAM | אינו כלול | אינו כלול | כלול | כלול |
| דף משותף של DLL או קובץ ממופה, מתגורר ב-RAM | כלול | בדרך כלל אינו כלול | בדרך כלל אינו כלול | כלול |
| Reserved אך לא committed | אינו כלול | אינו כלול | אינו כלול | עשוי להיכלל |
| טווח כתובות שאינו בשימוש | אינו כלול | אינו כלול | אינו כלול | בדרך כלל אינו כלול |
flowchart TB
accTitle: מיפוי סוגי דפים למדדי הזיכרון העיקריים
accDescr: מראה אילו מדדים כוללים דפים פרטיים מתגוררים, דפים פרטיים שאינם מתגוררים, דפים משותפים מתגוררים, וטווחים שמורים בלבד
privateResident["Private, committed, מתגורר ב-RAM"]
privateNonresident["Private, committed, אינו מתגורר ב-RAM"]
sharedResident["דף משותף, מתגורר ב-RAM"]
reservedOnly["Reserved, לא committed"]
workingSet["Working Set"]
privateWorkingSet["Private Working Set"]
privateBytes["Private Bytes"]
virtualBytes["משפחת Virtual Bytes"]
privateResident --> workingSet
privateResident --> privateWorkingSet
privateResident --> privateBytes
privateResident --> virtualBytes
privateNonresident --> privateBytes
privateNonresident --> virtualBytes
sharedResident --> workingSet
sharedResident --> virtualBytes
reservedOnly --> virtualBytes
איור 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 משתמש בטבלאות דפים כדי למפות כתובות וירטואליות לדפים פיזיים או לנתונים בקובץ.7
כתוצאה מכך, גם במחשב עם 64GB RAM מותקן, מרחב הכתובות הווירטואלי שתהליך 32 סיביות נתון יכול להשתמש בו קטן בהרבה בדרך כלל. ולהפך, זה גם נורמלי שתהליך 64 סיביות יהיה בעל מרחב כתובות וירטואלי גדול מה-RAM הפיזי.
3.2. Reserved אומר רק ש”הכתובת נתפסה”
MEM_RESERVE של VirtualAlloc שומר טווח כתובות וירטואלי רציף לשימוש עתידי. בשלב הזה אין אחסון פיזי שמשויך לדפים, ואי אפשר לקרוא מהטווח או לכתוב אליו.45
למשל, גם אם מסד נתונים או סביבת ריצה שומר טווח כתובות של 8GB לצמיחה עתידית, זה לבדו אינו צורך 8GB של RAM או 8GB של Private Bytes.
3.3. Committed הוא הבטחה “לגבות כשיהיה צורך”
MEM_COMMIT היא הפעולה שמעבירה דף וירטואלי למצב Committed וגורמת ל-Windows להבטיח את הגיבוי הדרוש. האם קריאה, כתיבה או הרצה מותרות בפועל נקבע בנפרד לפי הגנת הדף — PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE, PAGE_NOACCESS וכן הלאה — כך ש-Committed כשלעצמו אינו אומר “ניתן לקריאה ולכתיבה”. ברגע ה-commit זה נספר ל-Commit Charge של המערכת, אבל הדף הפיזי בפועל עשוי לא להיות מוקצה עד הגישה הראשונה. דף שנוגעים בו בפעם הראשונה מאותחל באפסים, עובר demand-zero fault, ונכנס ל-Working Set.51
לכן, אף על פי שקוראים לזה “הוקצה” בשני המקרים, בפועל יש שלושת השלבים הבאים.
flowchart TB
accTitle: שלושה שלבים מ-Reserve דרך Commit לשהייה ב-RAM
accDescr: מראה את הזרימה של שמירת כתובת וירטואלית, commit של הדף, והקצאת דף פיזי בגישה הראשונה עם כניסה ל-Working Set
reserve["MEM_RESERVE - שמירת טווח כתובות"]
reserve -.-> virtualMetric["משתקף במשפחת Virtual Bytes"]
reserve -->|MEM_COMMIT| committed["Committed - נגיש לפי הגנת הדף"]
committed -.-> commitMetric["משתקף ב-Private Bytes / System Commit"]
committed -->|גישה ראשונה, demand-zero fault| resident["הוקצה דף פיזי, מתגורר ב-RAM"]
resident -.-> workingSetMetric["משתקף ב-Working Set"]
committed -.->|אם לא ניגשו מעולם| nonresident["Committed אך אינו מתגורר"]
איור 4: Reserve, Commit והגישה הראשונה הם אירועים נפרדים, וכל אחד מזיז מדד אחר.
שלושת השלבים האלה מזיזים בנפרד, בהתאמה, את המספרים של משפחת Virtual Bytes, של Private Bytes ושל Working Set.
3.4. למה אפשר לקבל OutOfMemory גם כשיש RAM פנוי
האם הקצאת זיכרון מצליחה אינו נקבע לפי RAM פנוי לבדו.
- התהליך כילה את מרחב הכתובות הווירטואלי שלו
- אין טווח כתובות פנוי בגודל הנדרש שהוא רציף
- ה-Commit Charge ברמת המערכת הגיע ל-Commit Limit
- ל-Job Object, לקונטיינר, לסביבת ריצה או לספרייה יש מגבלה משלהם
- זה תהליך 32 סיביות
- ערימת ה-native מפורקת
גם ב-Windows של 64 סיביות, מרחב הכתובות הווירטואלי במצב משתמש של תהליך 32 סיביות הוא בדרך כלל 2GB אם IMAGE_FILE_LARGE_ADDRESS_AWARE אינו מוגדר. אפליקציית 32 סיביות עם הדגל הזה יכולה להשתמש עד 4GB ב-Windows של 64 סיביות.8
לכן “למחשב יש 20GB של RAM פנוי, ובכל זאת אפליקציית 32 הסיביות נכשלת סביב 1.6GB” אינו סתירה. זו אולי כלל לא בעיית RAM, אלא פיצול של מרחב הכתובות או פגיעה במגבלה קשיחה.
4. Working Set — דפים שנמצאים כרגע ב-RAM
Working Set הוא קבוצת הדפים, בתוך מרחב הכתובות הווירטואלי של תהליך, שכרגע מתגוררים ב-RAM הפיזי.1
הקבוצה הזו היא תערובת של הבאים.
- הערימה והמחסנית של התהליך עצמו
- קוד EXE ו-DLL ונתונים לקריאה בלבד
- קבצים שממופים לזיכרון
- זיכרון משותף
- דפים שהפכו פרטיים לאותו תהליך אחרי copy-on-write
- דפים שסביבת הריצה וספריות שונות נגעו בהם
4.1. Working Set שגדל אינו בהכרח אומר שהוקצה יותר
גישה בפעם הראשונה לדף שכבר היה committed יכולה להגדיל את ה-Working Set לבדו בזמן ש-Private Bytes נשאר ללא שינוי. באותו אופן, כשקובץ גדול ממופה לזיכרון ונקרא ברצף, דפים מגובים בקובץ נכנסים ל-Working Set בזמן ש-Private Bytes כמעט אינו גדל.
ולהפך, כש-Windows עושה Trim ל-Working Set בתגובה ללחץ זיכרון, ה-Working Set לבדו מצטמק בזמן שהאפליקציה מחזיקה לוגית את אותו זיכרון. נגיעה חוזרת אחר כך מחזירה אותו דרך Page Fault.
לכן ירידה ב-Working Set אינה בהכרח אומרת “האפליקציה שחררה”, ועלייה אינה בהכרח אומרת “האפליקציה הקצתה מחדש”.
flowchart TB
accTitle: זרימה טיפוסית שבה רק ה-Working Set עולה ויורד
accDescr: אותו דף committed נכנס ל-RAM בגישה הראשונה, הופך ללא-מתגורר ב-Trim, וחוזר בגישה חוזרת, בזמן ש-Private Bytes ממשיך להיספר
committed["אותו דף committed"]
committed -->|גישה ראשונה| resident["מתגורר ב-RAM"]
resident -->|Trim תחת לחץ זיכרון| nonresident["אינו מתגורר"]
nonresident -->|Page Fault בגישה חוזרת| resident
resident -.-> inWorkingSet["כלול ב-Working Set"]
nonresident -.-> outsideWorkingSet["אינו כלול ב-Working Set"]
committed -.-> privateBytes["נספר ב-Private Bytes כל עוד committed"]
איור 5: Working Set עולה ויורד עם השהייה, אבל Private Bytes אינו יורד כל עוד ה-Commit על אותו דף נשאר.
4.2. Working Set כולל דפים משותפים
אם 10 תהליכים חולקים את דפי הקוד של אותו DLL, הדף הזה יכול להופיע ב-Working Set של כל תהליך, אף על פי שבעותק אחד בלבד קיים ב-RAM הפיזי. סכום Working Set שחורג מה-RAM המותקן אינו מיד סימן לבעיה.
אם אתם רוצים להתקרב ל”ה-RAM שתהליך זה לבדו תופס כרגע”, הסתכלו על Private Working Set. גם אז, זה אינו “כל הזיכרון שאותו תהליך הקצה” — זה בדיוק הדפים הפרטיים שכרגע מתגוררים.
4.3. הורדה בכפייה של ה-Working Set אינה מתקנת דליפה
אפשר להשתמש ב-EmptyWorkingSet או ב-SetProcessWorkingSetSize כדי לפנות דפים מ-Working Set של תהליך. אבל זו אינה פעולה שמשחררת Commit או משחררת הפניות בערימה. שימוש ה-RAM הנראה יורד בזמן ש-Private Bytes נשאר ללא שינוי, והגישה הבאה יכולה להפעיל פרץ של תקלות דף.9
אם הנתון במנהל המשימות מצטמק רק מיד אחרי שלוחצים על כפתור “הפחתת זיכרון”, ומיד עולה בחזרה כשחוזרים לעבוד, זה עשוי להיות רק Trim של Working Set ולא “שחרור” אמיתי.
5. Private Bytes — כמות ה-Commit הפרטית לתהליך
Private Bytes הוא כמות הזיכרון הווירטואלי ש-committed באופן בלעדי לאותו תהליך. הוא מייצג Commit Charge שאי אפשר לשתף עם תהליך אחר, ולא משנה אם הוא כרגע מתגורר ב-RAM. ב-PROCESS_MEMORY_COUNTERS_EX של Microsoft, PrivateUsage מתאים לערך הזה.102
ל-Win32 API יש גם שדה בשם מבלבל, PagefileUsage, אבל התיעוד הנוכחי מגדיר אותו כ”Commit Charge של אותו תהליך” וקובע שזה אותו ערך כמו PrivateUsage. במילים אחרות, Private Bytes של 2GB אינו אומר “נכתבו 2GB ל-pagefile.sys”.2
Private Bytes מושפע בדרך כלל מהבאים.
- Commit של ערימת ה-native שמשמשים
HeapAlloc,malloc,newודומיהם - Private Data ש-committed ישירות עם
VirtualAlloc - האזור ה-committed של ערימת ה-GC של .NET
- החלק ממחסנית חוט ש-committed בפועל
- ה-Commit Charge לכל התצוגה שנשמר כשממפים תצוגת copy-on-write(
FILE_MAP_COPY) - מאגרים פרטיים שספריות ו-SDK של התקנים מחזיקים בפנים
בתצוגת copy-on-write שנוצרת עם FILE_MAP_COPY, כל דף יכול בסופו של דבר להפוך לפרטי, ולכן בזמן המיפוי Windows שומר Commit Charge מספיק כדי לגבות את כל התצוגה בקובץ הדפדוף. בגלל זה System Commit ו-Commit Charge של התהליך(Private Bytes)יכולים לעלות בגודל כל התצוגה עוד לפני שכתיבה כלשהי יוצרת בפועל עותק פרטי.11
5.1. למה Private Bytes אינו יורד אחרי free או GC
גם כשהזיכרון “משוחרר” מנקודת המבט של האפליקציה, סביבת הריצה או מקצה הערימה עשויים שלא לעשות Decommit של האזור בחזרה למערכת ההפעלה, אלא להחזיק אותו לשימוש חוזר עתידי. במקרה כזה Private Bytes נשאר גבוה אף על פי שהאזור ניתן לשימוש חוזר בפנים בתוך האפליקציה.
הוא יכול גם להישאר גבוה מסיבות כמו שרק חלק מאזור גדול עדיין חי, פיצול, או מטמון או מאגר שהתחממו עד התקרה.
לכן Private Bytes גבוה לבדו אינו מוכיח דליפה. מה שצריך להסתכל עליו הוא השוואה לאורך זמן:
- חזרו על אותו עיבוד אותו מספר פעמים
- המתינו אותו זמן אחרי העיבוד
- בדקו אם Private Bytes חוזר לאותה רמה, או מתייצב על ערך קבוע
- השתמשו ב-VMMap או ב-dump של ערימה כדי לבדוק איזה אזור או סוג גדל
flowchart TB
accTitle: למה Private Bytes אינו יורד אחרי free או GC
accDescr: Private Bytes משתנה אחרת בהתאם לשאלה אם המקצה מחזיר למערכת ההפעלה אזור שהאפליקציה כבר אינה צריכה, או מחזיק אותו לשימוש חוזר
release["האפליקציה משחררת אזור דרך free / GC"]
release --> decision{"האם המקצה מחזיר אותו למערכת ההפעלה"}
decision -->|Decommit / Release| returned["Commit Charge יורד"]
returned --> lower["Private Bytes יורד"]
decision -->|מחזיק לשימוש חוזר| retained["האזור נשאר committed"]
retained --> high["Private Bytes נשאר גבוה"]
retained --> reasons["מאגרים, מטמונים, פיצול"]
איור 6: אזור שהפך ניתן לשימוש חוזר בתוך האפליקציה אינו אותו דבר כמו החזרת ה-Commit שלו למערכת ההפעלה.
5.2. תבנית מועמדת חזקה לדליפה
עלייה כמו הבאה, שבה הרצפה עולה במדרגות עם כל סבב עומס, ראויה לתשומת לב.
Private Bytes
^
| ________
| ______|
| ______|
|_____|
+----------------------------> חזרות על אותו עיבוד
עם זאת, גם צורת מדרגות יכולה להיות רק כמה סבבי צמיחה מ-JIT בפעם הראשונה, גופנים, מפענחי תמונות, מאגרי חיבור או חימום מטמון, ואחריהם היא מתייצבת. מה שחשוב אינו שהיא גדלה, אלא שהיא נכשלת להתכנס למצב יציב.
6. System Commit — מה באמת “Committed X/Y”
הנתון “Committed X/Y” בלשונית [ביצועים] ← [זיכרון] של מנהל המשימות הוא מדד ברמת המערכת.
- X: System Commit Charge — הזיכרון ה-committed ש-Windows מבטיח כרגע לגבות בכל המערכת
- Y: System Commit Limit — תקרת ה-Commit שהמערכת יכולה לתמוך בה
Commit Limit נקבע בערך לפי RAM פיזי ועוד סך כל קובצי הדפדוף. בלי קובץ דפדוף, הוא יוצא קצת קטן מה-RAM המותקן.36
flowchart TB
accTitle: הקשר בין System Commit Charge ל-Commit Limit
accDescr: Commit פרטי לכל תהליך, של מקטעים משותפים ושל הליבה מרכיבים את הערך הנוכחי X, בעוד RAM פיזי וקובץ הדפדוף תומכים בתקרה Y
processCommit["Private Commit של כל תהליך"] --> charge["System Commit Charge - X"]
sharedCommit["Commit של מקטעים משותפים מגובים בקובץ דפדוף"] --> charge
kernelCommit["Commit של הליבה"] --> charge
physicalRam["RAM פיזי"] --> limit["System Commit Limit - Y"]
pageFiles["קובץ דפדוף"] --> limit
charge -->|X אינו יכול לעבור את Y| limit
איור 7: X הוא הכמות המובטחת הנוכחית ו-Y הוא התקרה שיכולה לתמוך בהבטחה הזו — זו אינה תצוגה של שימוש בקובץ הדפדוף.
System Commit Charge כולל לא רק את סכום ה-Private Bytes של כל תהליך, אלא גם את ה-Commit של מקטעים משותפים שמגובים בקובץ הדפדוף ואת ה-Commit שהליבה צורכת. לכן סכום ה-Private Bytes לפי תהליך לבדו אינו יכול להסביר את X במלואו.
6.1. Commit Charge אינו שימוש בקובץ הדפדוף
קחו מערכת עם 16GB RAM, קובץ דפדוף של 16GB, ו-Committed ב-20/31GB.
אותם 20GB אינם אומרים “נכתבו 20GB לקובץ הדפדוף”. זו הכמות הכוללת ש-Windows מבטיח לספק לה גיבוי של RAM או של קובץ דפדוף, כשיהיה צורך, לדפים פרטיים הניתנים לכתיבה וכדומה.
באותו רגע יכול להתקיים ערבוב של המצבים הבאים:
- רובו מתגורר ב-RAM
- חלקו פונה לקובץ הדפדוף
- חלקו committed אבל עדיין לא הייתה לו גישה ראשונה
- חלקו נצרך כ-Commit מצד הליבה
אם אתם רוצים לראות שימוש בפועל בקובץ הדפדוף, בדקו Paging File(*)\% Usage בנפרד מ-Commit. גם החומר של Microsoft עצמה מסביר ששימוש גבוה בקובץ הדפדוף לבדו אינו בהכרח מעיד על בעיית ביצועים, ושצריך לשפוט אותו יחד עם הגעה ל-Commit Limit, עם Modified Page List ועם קלט/פלט דפדוף בפועל.6
6.2. מה קורה כשמתקרבים ל-Commit Limit
כש-System Commit Charge מגיע ל-Commit Limit, אי אפשר לגבות בקשות commit חדשות. זה מוביל לכישלונות הקצאת זיכרון של תהליכים, לקריסות אפליקציות ולמערכת שאינה מגיבה.3
כאן X/Y של Commit חשוב יותר מ”RAM פנוי”. גם אם תעשו Trim ל-Working Set כדי לפנות RAM, הגעה ל-Commit Limit אינה נפתרת אלא אם ה-Commit Charge עצמו יורד.
6.3. שלושת התפקידים של קובץ הדפדוף
קובץ הדפדוף משרת בעיקר את התפקידים הבאים.
- הרחבת ה-Commit Limit
- לאפשר לפנות מ-RAM דפים שהשתנו ונמצאים בשימוש נדיר
- לגבות את dump הקריסה של המערכת, בהתאם להגדרה
השבתת קובץ הדפדוף אינה מקרה פשוט של “קלט/פלט דיסק תמיד יורד והדברים נעשים מהירים יותר”. אם כבר, היא מורידה את ה-Commit Limit, מגדילה את הסיכוי שדפים שהשתנו אבל כרגע אינם נחוצים יישארו ב-RAM, ויכולה להפוך לבלתי אפשרי לתפוס את ה-dump שאתם צריכים כשקריסה מתרחשת.36
את גודל קובץ הדפדוף המתאים אי אפשר להחליט לפי ה-RAM המותקן לבדו. Microsoft עצמה מסבירה שאי אפשר להכליל, כי שיא ה-System Commit Charge וסוג dump הקריסה הנדרש שונים ממערכת למערכת.6
7. פירוט ה-RAM הפיזי — אל תשפטו מ-Available נמוך לבדו
RAM פיזי אינו משמש רק את ה-Working Set של תהליכי משתמש.
- ה-Working Set של כל תהליך
- מטמון קובצי המערכת
- רשימות דפים כמו Standby, Modified, Free ו-Zeroed
- Paged Pool / Nonpaged Pool של הליבה
- זיכרון שמחזיקים מנהלי התקנים
- מחסן דחיסת הזיכרון
- אזורים משותפים עם ה-GPU והתקנים אחרים או שמורים להם
- זיכרון ששמור לחומרה
7.1. Available כולל גם מטמון שניתן לשימוש חוזר
Available MBytes של Windows אינו פשוט RAM שאינו בשימוש כלל. זה מדד שכולל, לצד Free ו-Zeroed, גם דפי Standby שאפשר להשתמש בהם מחדש אם צריך.12
- Free: דפים שאינם מוקצים כרגע לאף מטרה
- Zeroed: דפים שאופסו כך שאפשר למסור אותם בבטחה לתהליך אחר
- Standby: דפים שעזבו Working Set אבל תוכנם עדיין במטמון ב-RAM
- Modified: דפים שתוכנם השתנה ושצריך לכתוב אותם לגיבוי המתאים לפני שימוש חוזר
flowchart TB
accTitle: תנועה בין ה-Working Set לרשימות הדפים
accDescr: מראה דפים שלא השתנו שיוצאים ל-Standby ודפים שהשתנו שיוצאים ל-Modified, ואז גישה חוזרת, כתיבה ושימוש חוזר
workingSet["Working Set - בשימוש"]
workingSet -->|הוסר דף שלא השתנה| standby["Standby - מועמד לשימוש חוזר עם תוכן שנשמר"]
workingSet -->|הוסר דף שהשתנה| modified["Modified - ממתין לכתיבה"]
modified -->|הכתיבה הושלמה| standby
standby -->|גישה חוזרת| workingSet
standby -->|שימוש חוזר למטרה אחרת| reused["הוקצה למטרה אחרת"]
free["Free - אינו בשימוש"] -->|אופס| zeroed["Zeroed - זמין להקצאה חדשה"]
zeroed -->|גישה אחרי הקצאה| workingSet
standby -.-> available["כלול ב-Available"]
free -.-> available
zeroed -.-> available
איור 8: Available כולל לא רק זיכרון פנוי לגמרי אלא גם Standby, שאפשר להשתמש בו מחדש אם צריך.
“לזרוק את כל המטמון כדי להגדיל RAM פנוי” אינו תמיד ניצחון. אם הנתונים שאתם צריכים עדיין יושבים ב-Standby, גישה חוזרת יכולה להחזיר אותם ל-Working Set במהירות בלי לקרוא מהדיסק.
לכן גם אם Free נמוך במנהל המשימות, אם Available מרווח ותקלות דף קשות או המתנות דיסק אינן גורמות לבעיות, Windows פשוט עשוי להשתמש ב-RAM ביעילות כמטמון.
7.2. כשה-RAM מצטמק בלי תהליך גדול
אין זה נדיר שצריכת זיכרון תישאר בלתי מוסברת גם אחרי שמחברים את ה-Private Working Set של כל תהליך.
- מטמון קבצים וקבצים שממופים לזיכרון
- Nonpaged Pool / Paged Pool
- דפים שנעלו על ידי מנהל התקן
- דפים משותפים
- דחיסת זיכרון
- הקצאות הקשורות לווירטואליזציה או ל-GPU
במקרה כזה, במקום להמשיך לבהות ברשימת התהליכים, בדקו Use Counts, Processes, Priority Summary ו-File Summary ב-RAMMap של Sysinternals. RAMMap הוא הכלי הרשמי לפירוק זיכרון פיזי לפי מטרה, רשימת דפים וקובץ.13
אם רק Nonpaged Pool ממשיך לגדול, זו הנקודה שבה יש לחשוד בדליפה מצד מנהל התקן או הליבה, ולא ב-Private Bytes של אפליקציה במצב משתמש.
8. Page Fault — ספירה גבוהה אינה חריגה כשלעצמה
Page Fault מתרחש כשתהליך ניגש לדף שאינו כרגע ב-Working Set שלו. למרות המילה “Fault” בשם, זו אינה כשל חריג — זה המנגנון הרגיל שמניע זיכרון וירטואלי.1
8.1. Soft page fault
אלה נפתרים בלי לקרוא מהדיסק.
- הדף עדיין ב-Standby או ב-Transition
- אותו דף משותף כבר נמצא ב-Working Set של תהליך אחר
- ניגשים בפעם הראשונה לדף committed ומוקצה דף אפסים
- הקריאה מראש של מנהל הזיכרון כבר הכניסה אותו ל-RAM
לכן \Memory\Page Faults/sec גדול אינו בהכרח אומר שמתרחש קלט/פלט דיסק או השהיה.
8.2. Hard page fault
אלה דורשים לקרוא תוכן ממאגר גיבוי בדיסק. המקור אינו מוגבל לקובץ הדפדוף.
- קוד ונתונים ב-
.exeאו ב-.dll - קובץ שממופה לזיכרון
- קובץ הדפדוף
flowchart TB
accTitle: הענף בין soft page fault ל-hard page fault
accDescr: בגישה לדף שאינו ב-Working Set, מטפלים כ-soft page fault אם אין צורך בקלט/פלט אחסון, או כ-hard page fault אם יש
access["גישה לדף שאינו ב-Working Set"] --> storageIo{"האם נדרש קלט/פלט אחסון"}
storageIo -->|לא - Standby, משותף, demand-zero וכו| soft["Soft page fault"]
soft --> resident["נכנס ל-Working Set בלי קריאת דיסק"]
storageIo -->|כן| hard["Hard page fault"]
hard --> source{"מאיפה קוראים"}
source --> image["EXE / DLL"]
source --> mapped["קובץ שממופה לזיכרון"]
source --> pagefile["קובץ דפדוף"]
image --> loaded["נכנס ל-Working Set אחרי טעינה"]
mapped --> loaded
pagefile --> loaded
איור 9: השם “Page Fault” לבדו אינו יכול לומר לכם אם התרחש קלט/פלט דיסק.
Microsoft מונה את \Memory\Pages/sec, \Memory\Page Reads/sec ו-\Memory\Pages Input/sec בין המונים למדידת hard fault. מכיוון שגובהם אינו בהכרח אומר שחסר זיכרון, קשרו אותם ל-Available MBytes, להשהיית דיסק ולזמן תגובה בפועל.6
8.3. אל תציבו סף כולל אחד
ערך קבוע כמו “כל דבר מעל 1000 Page Faults/sec הוא חריג” משנה משמעות בהתאם לאחסון, לגודל דף, לעומס העבודה ולמקומיות הגישה.
בפועל, סדרו את הבאים על אותו ציר זמן.
Memory\Available MBytesMemory\Pages Input/secMemory\Page Reads/sec- השהיית קריאה / Queue בדיסק היעד
- Working Set ו-Private Bytes של תהליך היעד
- זמן העיבוד של האפליקציה, פסקי זמן ותגובתיות ממשק
אם 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, dump ייעודי לסביבת ריצה |
| דפדוף שמערב דיסק | Pages Input/sec, Page Reads/sec, השהיית דיסק | PerfMon, WPR/WPA |
flowchart TB
accTitle: בחירת כלי חקירת זיכרון של Windows
accDescr: הכלי לשימוש תלוי בשאלה אם היעד הוא תהליך אחד או כל המערכת, נקודה אחת בזמן או סדרת זמן, ואם צריך לעקוב אחרי החזקה בתוך סביבת ריצה
question["מה אתם רוצים לבודד"]
question --> processScope{"האם היעד הוא תהליך אחד"}
processScope -->|כן| processTime{"נקודה אחת או סדרת זמן"}
processTime -->|פירוט של נקודה אחת| vmmap["VMMap"]
processTime -->|סדרת זמן| perfmon["PerfMon / PowerShell"]
processScope -->|כל המערכת| systemView{"פירוט RAM פיזי או ציר זמן"}
systemView -->|פירוט RAM פיזי| rammap["RAMMap"]
systemView -->|ציר זמן כולל מעבד, קלט/פלט והמתנות| wpa["WPR / WPA"]
question --> runtime{"האם צריך לעקוב אחרי החזקה בתוך סביבת ריצה"}
runtime -->|ערימת .NET| dotnet["dotnet-dump / PerfView"]
runtime -->|ערימת native| native["WinDbg / 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
אם אתם יודעים את מזהה תהליך היעד, אפשר ללכוד יחד את מגמות Working Set, Private Bytes ו-Virtual Bytes עם Get-Process.
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
Process.WorkingSet64 של .NET מתאים ל-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 משותף, קובץ ממופה, מטמון קבצים | Image / Mapped File של VMMap, Pages Input/sec |
| Private Bytes עולה, Working Set יציב | Commit פרטי גדל אבל אינו מתגורר או עבר Trim | Heap / Private Data / Managed Heap של VMMap |
| שניהם עולים מיד אחרי ההפעלה, ואז מתיישרים | JIT, מטמון, מאגר, חימום אתחול | האם זה גדל שוב תחת אותו עומס נוסף |
| רצפת Private Bytes עולה עם כל סבב עומס | דליפה, מטמון בלי תקרה, או מקצה שמחזיק זיכרון אחרי שחרור | צילומי VMMap לפני ואחרי, dump של ערימה |
| רק Working Set צונח בפתאומיות וחוזר עם פעילות | מערכת ההפעלה או האפליקציה עשו Trim ל-Working Set | Private Bytes, Pages Input/sec, זמן תגובה |
| X ב-Committed X/Y מתקרב ל-Y | לחץ Commit ברמת המערכת | הצרכנים הגדולים של Private Bytes, Paged/Nonpaged Pool, הגדרות קובץ דפדוף |
| Available נמוך, Pages Input/sec והשהיית דיסק גבוהים | לחץ RAM פיזי ודפדוף קשה | הצרכנים הגדולים של Working Set, RAMMap, מתאם עומס |
| שימוש ב-RAM גבוה אבל אין תהליך גדול | מטמון, דפים משותפים, מאגרי ליבה, מנהלי התקנים, דחיסה וכו | RAMMap, Pool Nonpaged/Paged Bytes |
| יש RAM פנוי, ובכל זאת רק אפליקציית 32 הסיביות נכשלת | תקרת מרחב כתובות וירטואלי או פיצול | Free/Reserved של VMMap, הגדרת LAA של קובץ ההרצה |
| Private Bytes גבוה אבל אינו גדל בחזרה על העיבוד | מאגר או מטמון שאולי מחזיק סימן מים גבוה | המגבלה שלו, התנהגות שימוש חוזר, יציבות אחרי השיא |
הדבר החשוב ביותר בטבלה הזו הוא לקרוא אותה בשילוב, לא מערך בודד.
11. הליך מעשי לחקירת דליפת זיכרון
11.1. קבעו תחילה את תנאי השחזור ואת נקודת היציבות
“זה גדל במשך כמה ימים” לבדו אינו ניתן להשוואה.
- כמה מחימום שאחרי ההפעלה לכלול
- ממה מורכב מחזור פעולה אחד
- כמה שניות להמתין אחרי מחזור אחד
- כמה סבבים לוקח להגיע לתקרת המטמון
- האם אפשר להשתמש באותו קלט לגרסה הבריאה ולגרסה הבעייתית
החליטו את כל אלה.
11.2. רשמו את התהליך ואת המערכת יחד
לכל הפחות, שמרו את הבאים בחותמות זמן זהות.
- Working Set של היעד
- Private Bytes של היעד
- Virtual Bytes של היעד
- Committed Bytes / Commit Limit של המערכת
- Available MBytes
- Pages Input/sec
- מספר handles, מספר חוטים
- מספר פעולות או פריטים שעובדו
אם ה-Private Bytes של התהליך יציב בזמן ש-Commit של המערכת ממשיך לגדול, צריך להרחיב את ההיקף לתהליכים אחרים, לליבה, למנהלי התקנים ולמקטעים משותפים.
11.3. החליטו תחילה איזה “ממד” גדל
- Working Set לבדו: דפים מתגוררים, משותפים או שמקורם בקובץ, Trim וטעינה מחדש
- Private Bytes: Commit פרטי לתהליך
- Virtual Bytes לבדו: Reserve, מיפוי, פיצול מרחב כתובות
- System Commit לבדו: כולל תהליכים אחרים וצד הליבה
- Nonpaged Pool: צד מנהל התקן/ליבה
- Handles / GDI / USER: דליפות במשאבים שאינם זיכרון
לדלג על הסדר הזה ולקפוץ ישר ללקיחת dump פירושו לקרוא הר של מידע בזמן שמכוונים לדבר הלא נכון.
11.4. עברו לפירוט
- תהליך native: VMMap, WinDbg, Application Verifier, מעקב ערימה
- .NET:
dotnet-counters,dotnet-gcdump,dotnet-dump, PerfView - ברמת המערכת: RAMMap, PerfMon, WPR/WPA
- מאגר הליבה: PoolMon, WinDbg
VMMap מציג את הזיכרון הווירטואלי ה-committed של תהליך, ואת ה-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 הוא Commit Charge פרטי. זו כמות מובטחת לוגית שכוללת גם דפים שנמצאים כרגע ב-RAM וגם דפים שיגובו בקובץ הדפדוף אם וכאשר יהיה צורך.
תפיסה שגויה 3: Commit X/Y שווה לשימוש בקובץ הדפדוף / לקיבולת קובץ הדפדוף
ניסוח מחדש: X הוא Commit Charge ברמת המערכת, ו-Y הוא Commit Limit. קובץ הדפדוף מרחיב את Y, אבל X אינו מתרגם ישירות לשימוש בדיסק.
תפיסה שגויה 4: Page Faults/sec גבוה אומר שמחליפים לדיסק
ניסוח מחדש: זה כולל גם soft fault. בדקו 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 ואינו הכמות שנכתבה בפועל לקובץ הדפדוף.
- Committed X/Y הוא Commit Charge / Commit Limit ברמת המערכת. קובץ הדפדוף תומך בעיקר ב-Commit Limit, בפינוי דפים שהשתנו וב-dump של קריסה.
- כתובת וירטואלית Reserved, דף Committed, ודף שנגעו בו בפועל ונכנס ל-Working Set הם שלבים נפרדים.
- Page Fault הוא פעולה רגילה, ו-soft fault אינו קורא מהדיסק. גם hard fault יכולים להתרחש לא רק מקובץ הדפדוף אלא מ-EXE, DLL או קובץ ממופה.
- דליפת זיכרון מוכחת לא לפי הגודל בנקודת זמן אחת, אלא לפי ערך הרצפה והמגמה אחרי אותו עומס, יחד עם הפירוט.
- הנתיב הבסיסי: VMMap לפירוט של תהליך בודד, RAMMap ל-RAM פיזי ברמת המערכת, PerfMon לסדרת זמן, וכלי dump ייעודי למה שקורה בתוך סביבת ריצה.
בפעם הבאה שתשימו לב במנהל המשימות ש”הזיכרון גדל”, התחילו בשאלה הזו.
מה שגדל הוא Working Set, Private Bytes, Virtual Bytes או System Commit?
השאלה הזו לבדה הופכת את נקודת הכניסה לחקירה שלכם למדויקת בהרבה.
מאמרים קשורים
- להבחין בין עיכוב GC לדליפת זיכרון ב-.NET — הליך מעשי לצפייה, להשוואה ולהוכחה של גידול בזיכרון
- Process Explorer / Handle / VMMap בפועל — לרדוף אחרי תקיעות, דליפות ו”הקובץ בשימוש” ממצב הרגע
- המלכודות של הזיכרון המשותף ושיטות העבודה המומלצות בפועל
- מעמקי הקלט/פלט של Windows (חלק 4) — Cache Manager: מתי WriteFile שלכם באמת מגיע לדיסק?
- חקירת קריסה בהפעלה ממושכת של מצלמה תעשייתית — פרק דליפת ה-handle
תחומי ייעוץ קשורים
KomuraSoft LLC מטפלת בחקירות סיבת שורש שמשלבות PerfMon, VMMap, RAMMap, WinDbg וכלי אבחון של .NET לגידול זיכרון של אפליקציות Windows, להידרדרות ביצועים אחרי הפעלה ממושכת, ל-OutOfMemory בתהליכי 32 סיביות, ולמחסור בזיכרון שמתרחש רק בסביבת הלקוח. אנחנו לא עוצרים ב”הזיכרון גבוה” — אנחנו מבודדים איזה אזור גדל, דרך איזו פעולה, למה, ומאין מפנים אליו או מחזיקים אותו.
מקורות
-
Microsoft Learn, Working Set. על כך ש-Working Set של תהליך הוא קבוצת הדפים שכרגע מתגוררים בזיכרון פיזי, כולל דפים משותפים; על ההבדל בין soft page fault ל-hard page fault; על דפי Transition; ועל הסרת דפים מה-Working Set. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, PROCESS_MEMORY_COUNTERS_EX2 structure. על ההגדרות של WorkingSetSize, PrivateWorkingSetSize, PrivateUsage ו-SharedCommitUsage, ועל כך שגם PagefileUsage וגם PrivateUsage מייצגים את ה-Commit Charge של התהליך. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Introduction to page files. על כך שקובץ הדפדוף תומך בפינוי דפים שהשתנו, ב-dump של קריסת מערכת ובהרחבת System Commit Limit; על ההגדרות של System Commit Charge ו-Commit Limit; ועל אופן המדידה דרך מנהל המשימות ומוני ביצועים. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Page State. על מצבי Free, Reserved ו-Committed של דף וירטואלי, ועל כך שלדפים Reserved אין אחסון פיזי משויך והם אינם נגישים. ↩ ↩2
-
Microsoft Learn, VirtualAlloc function. על ההבדל בין MEM_RESERVE ל-MEM_COMMIT; על כך ש-commit מחויב כנגד הזיכרון הכולל של המערכת וקובץ הדפדוף; ועל כך שהדף הפיזי בפועל לפעמים אינו מוקצה עד הגישה הראשונה. ↩ ↩2 ↩3
-
Microsoft Learn, How to determine the appropriate page file size for 64-bit versions of Windows. על כך שגודל קובץ הדפדוף תלוי בשיא Commit Charge ובדרישות dump הקריסה; על כך ש-hard page fault קוראים לא רק מקובץ הדפדוף אלא גם מ-EXE, DLL וקבצים שממופים לזיכרון; ועל מוני הביצועים הקשורים. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Virtual Address Space. על כך שלכל תהליך יש מרחב כתובות וירטואלי עצמאי וטבלת דפים משלו, וכתובת וירטואלית אינה כתובת פיזית כשלעצמה. ↩
-
Microsoft Learn, Memory Limits for Windows and Windows Server Releases. על כך שמרחב הכתובות הווירטואלי במצב משתמש של תהליך 32 סיביות הוא בדרך כלל 2GB, והופך ל-2GB או ל-4GB ב-Windows של 64 סיביות בהתאם ל-IMAGE_FILE_LARGE_ADDRESS_AWARE. ↩
-
Microsoft Learn, SetProcessWorkingSetSize function. על כך שערכי המינימום והמקסימום של Working Set אינם מבטיחים שהייה; על היכולת לרוקן Working Set; ועל כך שהגדרות או פעולות מופרזות יכולות להרע את ביצועי המערכת. ↩
-
Microsoft Learn, Memory Performance Information. על ההתאמה בין מוני הביצועים של Windows, ממשקי ניהול הזיכרון ותצוגת מנהל המשימות, כולל Working Set / Working Set - Private / Private Bytes של אובייקט Process, ו-Committed Bytes / Commit Limit של אובייקט System. ↩
-
Microsoft Learn, MapViewOfFile function. על כך ש-
FILE_MAP_COPYהופך כל דף ל-copy-on-write פוטנציאלי, כך ש-Commit Charge לכל התצוגה נשמר כדי להיות מגובה בקובץ הדפדוף בזמן המיפוי. ↩ -
Microsoft Learn, Understanding Node Metrics and Properties in HPC Cluster Manager. על כך ש-Available Physical Memory מחושב כסכום רשימות Zeroed, Free ו-Standby, ועל משמעות כל אחת מרשימות הדפים האלה. ↩
-
Microsoft Sysinternals, RAMMap. על ניתוח שימוש הזיכרון הפיזי של Windows לפי מטרה, רשימת דפים, תהליך, עדיפות, דף פיזי וקובץ. ↩ ↩2
-
Microsoft Learn, Process.WorkingSet64 Property. על כך ש-
WorkingSet64מחזיר את ה-Working Set של התהליך בבתים, בהתאמה למונה הביצועים Working Set של אובייקט Process. ↩ -
Microsoft Learn, Process.PrivateMemorySize64 Property. על כך ש-
PrivateMemorySize64מחזיר את הזיכרון הפרטי לתהליך שאי אפשר לשתף עם תהליכים אחרים, בהתאמה למונה הביצועים Private Bytes. ↩ -
Microsoft Learn, Process.VirtualMemorySize64 Property. על כך ש-
VirtualMemorySize64מחזיר את כמות הזיכרון הווירטואלי שהוקצתה לתהליך, בהתאמה למונה הביצועים Virtual Bytes. ↩ -
Microsoft Sysinternals, VMMap. על פירוק הזיכרון הווירטואלי ה-committed של תהליך לפי סוג, ועל הצגת הזיכרון הפיזי(Working Set)שהוקצה לכל אחד, יחד עם מפת זיכרון מפורטת. ↩ ↩2
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
מעמקי זיכרון Windows (חלק 2) — חיי דף פיזי: חמש רשימות והאמת על קובץ ההחלפה
המאמר מחבר את מסד PFN, Standby, Modified, דחיסת זיכרון וקובץ ההחלפה ומסביר לאן הולך דף פיזי אחרי שהוא יוצא מ-Working Set.
מעמקי הזיכרון של Windows (חלק 1) — הרגע שבו כתובת וירטואלית הופכת ל-RAM פיזי: page fault מההתחלה ועד הסוף
המאמר מחבר VirtualAlloc, VAD, טבלאות דפים, TLB, demand-zero ושגיאות קשות ומסביר את הרגע שבו לכתובת וירטואלית מוקצה RAM פיזי.
Win32 Thread Pool API — מקביליות בלי ליצור תהליכונים, דרך CreateThreadpoolWork
מפזרים קריאות CreateThread בכל הקוד הנייטיב? המאמר מסביר את ה-API של מאגר התהליכונים של Win32 שעוצב מחדש ב-Vista — ארבעת האובייקטים work,...
Named Pipes בפועל — ה-IPC הסטנדרטי של Windows, מתכנון ועד אבטחה
מדריך מעשי ל-Named Pipes, התקשורת הסטנדרטית בין תהליכים ב-Windows. המאמר מסדר ממקורות ראשוניים את הבחירה בין מצב בתים למצב הודעות, תכנון ...
יישומים שנשברים בחזרה משינה — איך אירועי צריכת החשמל של Windows עובדים ואיך לבנות יישומים עסקיים ששורדים אותם
פתחתם את המחשב הנייד והחיבורים של היישום העסקי היו מתים — הסיבה היא תכנון שלא חשב על שינה. המאמר מכסה את זרימת ההודעות WM_POWERBROADCAST,...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
יישומים עסקיים, חיבור התקנים וכלי תקשורת, מהגדרת הדרישות ועד הפיתוח.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- האם עמודת "זיכרון" במנהל המשימות מציגה את כל הזיכרון שהאפליקציה הקצתה?
- לא. למנהל המשימות כמה עמודות זיכרון — משפחת 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 ובין אם לא. לכן השניים לעולם אינם שווים, ואף אחד מהם אינו גדול יותר מהשני באופן עקבי.
- האם "Committed 18/32GB" במנהל המשימות אומר שנכתבו 18GB לקובץ הדפדוף?
- לא. הנתון השמאלי הוא סך ה-Commit שהמערכת כולה מבטיחה כרגע לגבות; הנתון הימני הוא תקרת ה-Commit שהמערכת יכולה לתמוך בה. התקרה נקבעת בערך לפי RAM ועוד קובץ הדפדוף, אבל לא כל הסכום השמאלי יושב בפועל בקובץ הדפדוף. רוב הדפים ה-committed נמצאים ב-RAM, ולחלק מהדפים ה-committed מעולם לא הוקצה דף פיזי. בינתיים, דפים שאפשר לטעון מחדש מקובץ המקור שלהם — כמו EXE, DLL וקבצים שממופים לזיכרון — לא בהכרח מעלים את ה-Commit הפרטי באותה כמות שבה הם מעלים את ה-Working Set.
- האם אפשר לקבל OutOfMemory גם כשיש RAM פנוי?
- כן. הקצאה יכולה להיכשל מסיבות שאינן RAM פיזי, כולל תהליך 32 סיביות שמכלה את מרחב הכתובות הווירטואלי, מחסור בטווח כתובות פנוי רציף, תקרת ה-Commit של המערכת, או מגבלות ייחודיות ל-Job Object או לסביבת הריצה. בפרט, תהליך 32 סיביות ב-Windows של 64 סיביות מוגבל בדרך כלל ל-2GB של מרחב כתובות וירטואלי במצב משתמש, אלא אם הוא Large Address Aware.
- האם השבתת קובץ הדפדוף הופכת את Windows למהיר יותר?
- אי אפשר להניח זאת ככלל. השבתת קובץ הדפדוף מורידה את תקרת ה-Commit של המערכת, מקשה לפנות מ-RAM דפים שהשתנו ואינם בשימוש, ומשפיעה על אופן הגדרת dump של קריסה. גודל קובץ הדפדוף צריך להיקבע לפי מדידת שיא ה-Commit Charge ולפי ה-dump של הקריסה שאתם צריכים — זו אינה הגדרה שמשביתים בלי הצדקה.
- האם Page Faults/sec גבוה אומר שהמערכת קצרה בזיכרון?
- אי אפשר לדעת מזה לבדו. תקלות דף כוללות soft fault, שאפשר לפתור מדפי Standby ב-RAM או מדפים משותפים עם תהליך אחר, ו-hard fault שקוראים מהדיסק. במקום להסתכל על Page Faults/sec בבידוד, בדקו Pages Input/sec, Page Reads/sec, Available MBytes, השהיית דיסק וזמן עיבוד יחד על אותו ציר זמן.