פתרון בעיות בזיכרון

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

סיכום

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

סקירה כללית

בהתאם למודל הביצועים RAIL, מומלץ להתמקד במשתמשים כשאתם משפרים את הביצועים.

חשוב לבדוק בעיות זיכרון כי לרוב המשתמשים יכולים להבחין בהן. משתמשים יכולים להבחין בבעיות שקשורות לזיכרון בדרכים הבאות:

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

צבירת זיכרון: כמה זה "יותר מדי"?

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

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

המפתח כאן הוא להשתמש במודל RAIL ולהתמקד במשתמשים שלך. כדאי לבדוק אילו מכשירים פופולריים בקרב המשתמשים שלכם, ואז לבדוק את הדף במכשירים האלה. אם החוויה גרועה באופן עקבי, יכול להיות שהדף חורג מיכולות הזיכרון של המכשירים האלה.

מעקב אחרי השימוש בזיכרון בזמן אמת באמצעות מנהל המשימות של Chrome

אפשר להשתמש ב'מנהל המשימות' של Chrome כנקודת התחלה לחקירה של בעיות בזיכרון. מנהל המשימות הוא מכשיר למעקב בזמן אמת שמראה כמה זיכרון מנוצל כרגע בדף.

  1. כדי לפתוח את מנהל המשימות, מקישים על Shift+Esc או עוברים לתפריט הראשי של Chrome ובוחרים באפשרות כלים נוספים > מנהל משימות.

    פתיחת מנהל המשימות

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

    הפעלת זיכרון JS

שתי העמודות האלה מספקות מידע שונה על אופן השימוש של הדף בזיכרון:

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

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

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

  1. פותחים את החלונית ביצועים ב-DevTools.
  2. מסמנים את התיבה זיכרון.
  3. ביצוע הקלטה.

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

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

בכל פעם שלוחצים על הלחצן שמופיע בקוד, עשרת אלפים צמתים div מתווספים לגוף המסמך ומחרוזת של מיליון תווי x מועברת למערך x. הרצת הקוד הזה יוצרת הקלטה ב-Timeline כמו בצילום המסך הבא:

דוגמה לצמיחה פשוטה

קודם, הסבר על ממשק המשתמש. התרשים HEAP בחלונית Overview (מתחת ל-NET) מייצג את אשכול ה-JS. מתחת לחלונית סקירה כללית מופיעה חלונית ספירה לאחור. כאן אפשר לראות פירוט של השימוש בזיכרון לפי ערימה של JavaScript (זהה לתרשים HEAP בחלונית Overview), מסמכים, צומתי DOM, מאזינים וזיכרון של GPU. השבתת תיבת סימון מסתירה אותה בתרשים.

עכשיו, ניתוח הקוד בהשוואה לצילום המסך. אם מעיינים בספירת הצמתים (התרשים הירוק), אפשר לראות שהיא תואמת לקוד. מספר הצמתים גדל בשלבים נפרדים. אפשר להניח שכל הגדלה בספירת הצמתים היא קריאה ל-grow(). תרשים אשכול ה-JS (התרשים הכחול) לא פשוט כל כך. בהתאם לשיטות המומלצות, הירידה הראשונה היא למעשה איסוף אשפה מאולץ (שמתקבל על ידי לחיצה על הלחצן collect garbage). ככל שההקלטה מתקדמת, אפשר לראות עליות חדות בגודל הערימה של JS. זהו מצב טבעי וצפוי: קוד JavaScript יוצר את צמתים ה-DOM בכל לחיצה על הלחצן ומבצע הרבה עבודה כשיוצר את המחרוזת של מיליון התווים. הדבר החשוב כאן הוא העובדה שהערימה של JS מסתיימת ברמה גבוהה יותר מזו שבה היא התחילה ('ההתחלה' כאן היא הנקודה אחרי איסוף האשפה המאולץ). בעולם האמיתי, אם ראינו את הדפוס הזה של הגדלת גודל הערימה (heap) או גודל הצומת של JS, ייתכן שזה סימן לדליפת זיכרון.

איתור דליפות זיכרון בעץ DOM מנותק באמצעות קובצי snapshot של ערימות

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

הנה דוגמה פשוטה לצמתים DOM מופרדים.

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

לחיצה על הלחצן שמופיע בקוד יוצרת צומת ul עם עשרה צאצאים מסוג li. יש לקוד הפניה לצמתים האלה, אבל הם לא קיימים בעץ ה-DOM, ולכן הם מנותקים.

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

כדי ליצור קובץ snapshot, פותחים את DevTools ועוברים לחלונית Memory, בוחרים בלחצן הבחירה Heap Snapshot ואז לוחצים על הלחצן Take Snapshot.

צילום תמונת מצב של הזיכרון

עיבוד וטעינה של קובץ ה-snapshot עשויים להימשך זמן מה. בסיום, בוחרים בו בחלונית הימנית (שנקראת HEAP SNAPSHOTS).

מקלידים Detached בתיבת הטקסט Class filter כדי לחפש עצי DOM מנותקים.

סינון לצמתים מנותקים

מרחיבים את הסמנים כדי לבדוק עץ מנותק.

בדיקה של עץ מנותק

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

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

בדיקה של צומת צהוב

זיהוי דליפות זיכרון בערימה של JS באמצעות צירי זמן של הקצאות

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

כדי להמחיש את ציר הזמן להקצאה, אפשר להשתמש בקוד הבא:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

בכל פעם שמקישים על הלחצן שמצוין בקוד, מחרוזת של מיליון תווים מתווספת למערך x.

כדי לתעד ציר זמן של הקצאות, פותחים את DevTools, עוברים לחלונית Profiles, בוחרים בלחצן הבחירה Record Allocation Timeline, לוחצים על הלחצן Start, מבצעים את הפעולה שסביר להניח שהיא גורמת לדליפה בזיכרון, ולוחצים על הלחצן stop recording (לחצן להפסקת ההקלטה) בסיום.

בזמן הצילום, שימו לב אם מופיעים פסים כחולים בציר הזמן של ההקצאה, כמו בצילום המסך שבהמשך.

הקצאות חדשות

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

ציר זמן של הקצאה בהגדלת מרחק התצוגה

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

פרטי האובייקט

בדיקת הקצאת הזיכרון לפי פונקציה

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

כלי לניתוח ביצועים של הקצאת רשומות

  1. בוחרים בלחצן הבחירה Allocation Sampling. אם יש דף עבודה בדף, אפשר לבחור אותו כיעד ליצירת פרופיל באמצעות התפריט הנפתח לצד הלחצן Start.
  2. לוחצים על הלחצן התחלה.
  3. מבצעים את הפעולות בדף שרוצים לחקור.
  4. כשמסיימים את כל הפעולות, לוחצים על הלחצן עצירה.

ב-DevTools מוצג פירוט של הקצאת הזיכרון לפי פונקציה. תצוגת ברירת המחדל היא Heavy (Bottom Up), שבה הפונקציות שהוקצו להן הכי הרבה זיכרון מוצגות בחלק העליון.

פרופיל הקצאה

איתור אוספים תכופים של אשפה

אם נראה שהדף שלכם מושהה לעיתים קרובות, יכול להיות שיש בעיות באיסוף האשפה.

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

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