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

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

סיכום

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

סקירה

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. פותחים את החלונית ביצועים בכלי הפיתוח.
  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. הרצת הקוד הזה יוצרת הקלטה של ציר הזמן, כמו צילום המסך הבא:

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

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

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

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

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

דוגמה פשוטה לצומתי 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 ולכן הם מנותקים.

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

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

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

עיבוד תמונת המצב עשוי להימשך זמן מה. כשמסיימים, בוחרים אותו בחלונית השמאלית (בשם HEAP SNAPSHOTS).

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

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

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

חקירה של עץ נותק

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

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

חקירה של צומת צהוב

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

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

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

var x = [];

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

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

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

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

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

הקצאות חדשות

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

ציר הזמן להקצאה עם מרחק תצוגה

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

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

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

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

הקלטת פרופיל ההקצאה

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

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

פרופיל הקצאה

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

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

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

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