צילום תמונות מצב של ערימה (heap snapshot)

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

רוצים לדעת איך לצלם תמונות מצב של ערימה (heap snapshot) באמצעות זיכרון > פרופילים > תמונת מצב של ערימה (heap snapshot) ואיתור דליפות זיכרון.

הכלי לניתוח נתוני ערימה מציג את התפלגות הזיכרון לפי אובייקטי ה-JavaScript של הדף וצומתי DOM קשורים. אפשר להשתמש בו כדי לצלם תמונות מצב של ערימה של JS, לנתח תרשימי זיכרון, להשוות תמונות מצב ולאתר דליפות זיכרון. אפשר לקרוא מידע נוסף במאמר בנושא עץ אובייקטים ששומרים.

צלם תמונה

כדי לצלם תמונת מצב של ערימה (heap snapshot):

  1. בדף שרוצים ליצור לו פרופיל, פותחים את כלי הפיתוח ועוברים לחלונית זיכרון.
  2. בוחרים בסוג יצירת הפרופיל radio_button_checked eap snapshot, בוחרים מכונת JavaScript ו-VM ולוחצים על Take snapshot (צילום תמונת מצב).

נבחר סוג יצירת פרופיל ומכונת JavaScript VM.

כשהחלונית זיכרון נטענת ומנתחת את תמונת המצב, הגודל הכולל של אובייקטים מסוג JavaScript שניתן לגשת אליהם מוצג מתחת לכותרת של תמונת המצב, בקטע HEAP SNAPSHOTS.

הגודל הכולל של האובייקטים שניתן להגיע אליהם.

צילומי מסך מציגים רק את האובייקטים מתרשים הזיכרון שניתן להגיע אליהם מהאובייקט הגלובלי. צילום תמונת מצב מתחיל תמיד באיסוף אשפה.

תמונת מצב של ערימה (heap snapshot) של אובייקטים מפוזרים של פריטים.

ניקוי תמונות מצב

כדי להסיר את כל תמונות המצב, לוחצים על חסימה ניקוי כל הפרופילים:

ניקוי כל הפרופילים.

הצגת תמונות מצב

כדי לבחון תמונות מצב מנקודות מבט שונות למטרות שונות, בוחרים באחת מהתצוגות מהתפריט הנפתח שבחלק העליון:

מעבר למצב תצוגה Content מטרה
סיכום אובייקטים מקובצים לפי שמות של בנאים. אפשר להשתמש בו כדי לצוד אובייקטים ואת השימוש שלהם בזיכרון על סמך סוג. שימושי למעקב אחר דליפות DOM.
השוואה ההבדלים בין שתי תמונות מצב. תוכלו להשתמש בו כדי להשוות בין שתי תמונות מצב (או יותר), לפני ואחרי פעולה. כדי לאשר את הנוכחות ואת הסיבה לדליפת זיכרון, בודקים את הדלתא בזיכרון שהתפנה ובספירת ההפניות.
בלימה תוכן של ערימה (heap) מספקת תצוגה טובה יותר של מבנה האובייקטים ועוזרת לנתח אובייקטים שמתבצעת אליהם הפניה במרחב השמות הגלובלי (חלון) כדי לגלות מה משאיר אותם בסביבה. אפשר להשתמש בו כדי לנתח סגירות ולצלול לעומק האובייקטים שלכם ברמה נמוכה.
נתונים סטטיסטיים תרשים עוגה של הקצאת זיכרון הצגת הגדלים האמיתיים של חלקי זיכרון שהוקצו לקוד, למחרוזות, למערכי JS, למערכים מוקלדים ולאובייקטים של מערכת.

תצוגת הסיכום שנבחרה בתפריט הנפתח שבחלק העליון של הדף.

תצוגת סיכום

בשלב הראשון, נפתחת תמונת מצב של הזיכרון (heap snapshot) בתצוגה סיכום שבה מופיעים המרכיבים בעמודה. אפשר להרחיב את הבנאים כדי לראות את האובייקטים שהם יצרו.

תצוגת הסיכום עם מבנה מורחב.

כדי לסנן בנאים לא רלוונטיים, מקלידים שם שרוצים לבדוק במסנן מחלקה בחלק העליון של התצוגה סיכום.

המספרים שמופיעים ליד השמות של הבנאים מציינים את המספר הכולל של האובייקטים שנוצרו עם הבנאי. בתצוגה סיכום מוצגות גם העמודות הבאות:

  • מרחק מציג את המרחק לשורש באמצעות המסלול הפשוט ביותר לצמתים.
  • גודל רדוד מציג את סכום הגדלים הרדודים של כל האובייקטים שנוצרו על ידי בנאי מסוים. 'גודל רדוד' הוא גודל הזיכרון שהאובייקט עצמו שמור בו. באופן כללי, למערכים ולמחרוזות יש גדלים רדודים וגדולים יותר. ראו גם גדלים של אובייקטים.
  • גודל שנשמר מציג את הגודל המקסימלי שנשמר בין אותה קבוצת אובייקטים. הגודל שנשמר הוא גודל הזיכרון שניתן לשחרר על ידי מחיקת אובייקט וביטול הגישה של הגורמים התלויים בו. ראו גם גדלים של אובייקטים.

כשמרחיבים בנאי, התצוגה סיכום מציגה את כל המופעים שלו. בעמודות המתאימות מופיע פירוט של הגדלים הרדודים והשמורים של כל מכונה. המספר שמופיע אחרי התו @ הוא המזהה הייחודי של האובייקט. הוא מאפשר להשוות תמונות מצב של ערימה לפי אובייקט.

ערכים מיוחדים בסיכום

בנוסף לקיבוץ לפי מרכיבים, התצוגה סיכום מקבצת גם אובייקטים לפי:

  • פונקציות מובנות כמו Array או Object.
  • פונקציות שהגדרתם בקוד.
  • קטגוריות מיוחדות שאינן מבוססות על בנאים.

רשומות Constructor.

(array)

הקטגוריה הזו כוללת מגוון אובייקטים פנימיים דמויי מערך, שלא תואמים ישירות לאובייקטים שמוצגים ב-JavaScript.

לדוגמה, התוכן של אובייקטים מסוג Array של JavaScript מאוחסן באובייקט פנימי משני בשם (object elements)[], כדי לאפשר שינוי גודל בקלות. באופן דומה, המאפיינים בעלי השם באובייקטים של JavaScript מאוחסנים בדרך כלל באובייקטים פנימיים משניים בשם (object properties)[], שרשומים גם בקטגוריה (array).

(compiled code)

הקטגוריה הזו כוללת נתונים פנימיים שדרושים ל-V8 כדי להריץ פונקציות שהוגדרו על ידי JavaScript או WebAssembly. אפשר לייצג כל פונקציה במגוון דרכים, מקטינה ואיטית ועד גדולה ומהירה.

V8 מנהל באופן אוטומטי את השימוש בזיכרון בקטגוריה הזו. אם פונקציה מסוימת פועלת הרבה פעמים, V8 מנצל יותר זיכרון לאותה פונקציה כדי שהיא תוכל לפעול מהר יותר. אם פונקציה מסוימת לא פועלת כבר זמן מה, V8 עשוי למחוק את הנתונים הפנימיים שלה.

(concatenated string)

כאשר V8 משורשר שתי מחרוזות, למשל באמצעות האופרטור + של JavaScript, הוא עשוי לבחור לייצג את התוצאה באופן פנימי כ'מחרוזת משורשרת', שנקראת גם מבנה הנתונים Rope (חבל).

במקום להעתיק את כל התווים בשתי מחרוזות המקור למחרוזת חדשה, V8 מקצה אובייקט קטן עם שדות פנימיים שנקראים first ו-second, שמצביעים לשתי מחרוזות המקור. כך מתאפשרת ל-V8 לחסוך זמן וזיכרון. מבחינת קוד JavaScript, אלו הן מחרוזות רגילות, והן מתנהגות כמו כל מחרוזת אחרת.

InternalNode

הקטגוריה הזו מייצגת אובייקטים שהוקצו מחוץ ל-V8, כמו אובייקטים של C++ שהוגדרו על ידי Blink.

כדי לראות שמות של כיתות C++, יש להשתמש ב-Chrome for Testing ולבצע את הפעולות הבאות:

  1. פותחים את כלי הפיתוח ומפעילים את ההגדרות הגדרות > ניסויים > check_box הצגת האפשרות לחשוף תמונות פנימיות של תמונות מצב של ערימה.
  2. פותחים את החלונית זיכרון, בוחרים באפשרות radio_button_checked תמונת מצב של ערימה ומפעילים את radio_button_checked חשיפה של רכיבים פנימיים (כולל פרטים נוספים שספציפיים להטמעה).
  3. משחזרים את הבעיה שגרמה ל-InternalNode לשמור כמות גדולה של זיכרון.
  4. צילום תמונת מצב של הזיכרון (heap snapshot). בתמונת המצב הזו, לאובייקטים יש שמות מחלקה C++ במקום InternalNode.
(object shape)

כפי שמתואר במאפיינים מהירים ב-V8, V8 עוקב אחר מחלקות נסתרות (או צורות) כך שניתן לייצג ביעילות מספר אובייקטים עם אותם מאפיינים באותו סדר. הקטגוריה הזו מכילה את המחלקות המוסתרות האלו, שנקראות system / Map (לא קשורות ל-JavaScript Map), ונתונים קשורים.

(sliced string)

כאשר V8 צריך לקבל מחרוזת משנה, למשל כשקוד JavaScript קורא ל-String.prototype.substring(), V8 עשוי להקצות אובייקט מחרוזת פרוסה במקום להעתיק את כל התווים הרלוונטיים מהמחרוזת המקורית. האובייקט החדש מכיל מצביע למחרוזת המקורית, ומתאר את טווח התווים מהמחרוזת המקורית שבו צריך להשתמש.

מבחינת קוד JavaScript, אלו הן מחרוזות רגילות, והן מתנהגות כמו כל מחרוזת אחרת. אם מחרוזת פרוסה שומרת כמות גדולה של זיכרון, ייתכן שהתוכנה הפעילה את בעיה 2869 וכדאי לבצע פעולות כוונה אל "השטח" של המחרוזת.

system / Context

אובייקטים פנימיים מסוג system / Context מכילים משתנים מקומיים מסגירה – היקף של JavaScript שפונקציה מקננת יכולה לגשת אליו.

כל מופע של פונקציה מכיל מצביע פנימי ל-Context שבו הוא מופעל, כדי שהוא יוכל לגשת למשתנים האלה. למרות שאובייקטים של Context לא גלויים באופן ישיר ב-JavaScript, יש לך שליטה ישירה עליהם.

(system)

קטגוריה זו מכילה אובייקטים פנימיים שונים אשר (עדיין) לא סווגו באופן משמעותי יותר.

תצוגת השוואה

התצוגה השוואה מאפשרת לכם לאתר אובייקטים שדלפו על ידי השוואה בין כמה תמונות מצב. לדוגמה, ביצוע פעולה והיפוך הפעולה, כמו פתיחת מסמך וסגירתו, לא אמורות להשאיר אובייקטים מיותרים מאחור.

כדי לוודא שפעולה מסוימת לא יוצרת הדלפות:

  1. צריך לצלם תמונת מצב של ערימה לפני ביצוע פעולה.
  2. מבצעים פעולה. כלומר, יש לבצע אינטראקציה עם דף באופן כלשהו שלדעתכם גורם לדליפה.
  3. מבצעים פעולה הפוכה. כלומר, מבצעים את האינטראקציה ההפוכה וחוזרים עליה כמה פעמים.
  4. מצלמים תמונת מצב של תמונת מצב של הערימה השנייה ומשנים את התצוגה ל-Comparison (השוואה) ל-Snapshot 1.

בתצוגת ההשוואה מוצג ההבדל בין שתי תמונות מצב. כשמרחיבים ערך כולל, מוצגים מופעי אובייקטים שנוספו ונמחקו:

השוואה לתמונת המצב 1.

תצוגת מאגרים

התצוגה Containment (מאגר) היא תצוגה של מבנה האובייקטים באפליקציה. הוא מאפשר לכם לקבל הצצה פנימה לגבי סגירת פונקציות, לצפות באובייקטים פנימיים של מכונות וירטואליות שמרכיבים את האובייקטים של JavaScript, ולהבין בכמה זיכרון האפליקציה שלכם משתמשת ברמה נמוכה מאוד.

לתצוגה יש מספר נקודות כניסה:

  • אובייקטים של DOMWindow. אובייקטים גלובליים לקוד JavaScript.
  • שורשי GC. שורשי GC שמשמשים את אוסף האשפה של המכונה הווירטואלית. שורשי GC יכולים לכלול מפות אובייקטים מובנות, טבלאות סמלים, ערימות שרשורי VM, מטמוני הידור, היקפים של נקודות אחיזה ונקודות אחיזה גלובליות.
  • אובייקטים מקוריים. אובייקטים של דפדפן "נדחפו" אל המכונה הווירטואלית של JavaScript כדי לאפשר אוטומציה, למשל, צומתי DOM וכללי CSS.

התצוגה 'מאגר'.

הקטע של השימורים

בקטע השימורים שבתחתית החלונית זיכרון מוצגים אובייקטים שמצביעים לאובייקט שנבחר בתצוגה.

הקטע 'השימורים'.

בדוגמה הזו, המחרוזת שנבחרה נשמרת על ידי המאפיין x של מכונה של Item.

מצא אובייקט ספציפי

כדי למצוא אובייקט בערימה שנאספו, אפשר לחפש באמצעות Ctrl + F ולהזין את מזהה האובייקט.

מתן שמות לפונקציות כדי להבחין בין סגירות

כדאי מאוד לתת שם לפונקציות כדי שתוכלו להבחין בין חסימות בתמונת המצב.

לדוגמה, הקוד הבא לא כולל פונקציות בעלות שם:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

כל עוד דוגמה זו כן:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

פונקציה בעלת שם בסגירה.

גילוי דליפות DOM

כלי המצב של הערימה (heap profiler) יכול לשקף יחסי תלות דו-כיווניים בין אובייקטים מקוריים בדפדפן (צומתי DOM וכללי CSS) לבין אובייקטים של JavaScript. התכונה הזו עוזרת לגלות דליפות בלתי נראות אחרת שמתרחשות עקב תתי-עצים של DOM מנותקים שמרחפים מסביב.

דליפות DOM יכולות להיות גדולות יותר ממה שאתם חושבים. עיינו בדוגמה הבאה. מתי נאספים האשפה של #tree?

  var select = document.querySelector;
  var treeRef = select("#tree");
  var leafRef = select("#leaf");
  var body = select("body");

  body.removeChild(treeRef);

  //#tree can't be GC yet due to treeRef
  treeRef = null;

  //#tree can't be GC yet due to indirect
  //reference from leafRef

  leafRef = null;
  //#NOW #tree can be garbage collected

ל-#leaf יש הפניה להורה (parentNode) ובאופן רקורסיבי עד #tree, כך שרק כשהעץ leafRef null הוא עץ כל ב-#tree, מועמד ל-GC.

עצי משנה של DOM