סקירה כללית של ניהול הזיכרון

השימוש במכונה הווירטואלית של Android (ART) וב-Dalvik חלוקה לדפים ומיפוי זיכרון (רנדומיזציה) כדי לנהל את הזיכרון. המשמעות היא שכל זיכרון שמשתמשים משנים – בין אם על ידי הקצאת אובייקטים חדשים ובין אם על ידי שימוש בדפים ממופה – נשאר ב-RAM ולא ניתן להעביר אותו לדף אחר. הדרך היחידה לשחרר את הזיכרון מאפליקציה היא לשחרר לאובייקטים שמאוחסנים באפליקציה, וכך הזיכרון יהיה זמין אוסף אשפה. לעומת זאת, יש חריג אחד: כל הקבצים כלום ללא שינויים, כמו קוד, עשוי להיות מוצג מחוץ ל-RAM אם המערכת רוצה להשתמש בזיכרון הזה במקום אחר.

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

איסוף אשפה

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

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

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

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

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

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

שיתוף הזיכרון

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

  • כל תהליך אפליקציה מפוצל מתהליך קיים שנקרא Zygote. תהליך Zygote מתחיל כשהמערכת אתחול וטוענת משאבים וקוד framework (כמו נושאי פעילות). כדי להתחיל תהליך אפליקציה חדש, המערכת יוצרת צאצא לתהליך Zygote ואז טוענת את הקוד של האפליקציה ומריצה אותו בתהליך החדש. גישה זו מאפשרת לרוב דפי ה-RAM שהוקצו את ה-framework ואת המשאבים לשיתוף בכל תהליכי האפליקציה.
  • רוב הנתונים הסטטיים ממופים לתהליך. השיטה הזו מאפשרת לשתף נתונים בין תהליכים, והוא גם מאפשר ליצור דפים כשיש צורך. נתונים סטטיים לדוגמה: קוד Dalvik (על ידי הצבתו בתוך .odex מקושר מראש קובץ ל-mapping ישיר), משאבי אפליקציות (על ידי תכנון טבלת המשאבים כך שתהיה מבנה שאפשר לדחוס אותם ועל ידי יישור הרוכסן של ה-APK), כמו קוד מקורי בקובצי .so.
  • במקומות רבים, Android משתף את אותה זיכרון RAM דינמי בין תהליכים באמצעות אזורים של זיכרון משותף שהוקצתה להם באופן מפורש (באמצעות ashmem או gralloc). לדוגמה, פלטפורמות החלונות כוללות את הזיכרון בין האפליקציה למרכיב המסך, וגם בתהליך האחסון הזמני של הסמן משתמשים בזיכרון המשותף בין וספק תוכן.

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

הקצאה וניצול מחדש של זיכרון האפליקציה

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

הגודל הלוגי של הערימה לא זהה לזה כמות הזיכרון הפיזי שנוצל על ידי הערימה. כשבודקים את נפח הערימה של האפליקציה, מערכת Android מחשבת את הנתונים ערך שנקרא 'גודל קבוצה פרופורציונלית' (PSS), שמטפל גם בדפים מלוכלכים וגם בדפים נקיים שמשותפים עם תהליכים אחרים, אבל רק באופן יחסי למספר האפליקציות שמשתפות שיש RAM. הסכום הכולל (PSS) הוא מה שהמערכת נחשב לטביעת הרגל הפיזית של הזיכרון. מידע נוסף על PSS זמין במאמר בדיקת השימוש ב-RAM מותאמת אישית.

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

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

כדי לשמור על סביבת ריבוי משימות פונקציונלית: מערכת Android מגדירה מגבלה קשיחה על גודל הערימה לכל אפליקציה. מגבלת הגודל המדויק של הזיכרון משתנה בין מכשירים לפי נפח ה-RAM שהמכשיר יש באופן כללי. אם האפליקציה הגיעה אל קיבולת ערימה ומנסה להקצות יותר הוא יכול לקבל OutOfMemoryError.

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

מעבר בין אפליקציות

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

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

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

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

בדיקת עומס על הזיכרון

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

בדיקת אפליקציה במצב לחץ

בדיקת לחץ על אפליקציה (stressapptest) היא בדיקה של ממשק זיכרון שעוזרת ליצור מצבים ריאליסטיים של עומס גבוה כדי לבדוק מגבלות שונות של זיכרון וחומרה באפליקציה. היכולת להגדיר מגבלות זמן ומגבלות זיכרון מאפשרת לכתוב כלים למדידת ביצועים כדי לוודא שהאפליקציה תתמודד עם מצבים של זיכרון רב בעולם האמיתי. לדוגמה, השתמשו בקבוצת הפקודות הבאה כדי לדחוף את הספרייה הסטטית במערכת קובצי הנתונים שלכם, להפוך אותו לקובץ הפעלה ולהריץ בדיקת לחץ במשך 20 שניות מתוך 990MB:
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

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

תצפיות ב-stressapptest

ניתן להשתמש בכלים כמו stressapptest כדי לבקש הקצאות זיכרון שגדולות יותר מבאופן חופשי זמינים. בקשה מהסוג הזה יכולה לשלוח התראות שונות, ושעליכם להיות מודעים להן צד הפיתוח. שלוש התראות עיקריות שאפשר להעלות בגלל זמינות נמוכה של הזיכרון:
  • SIGABRT: זוהי קריסה חמורה, מובנית לתהליך שלך עקב בקשה להקצאות של גדול יותר מהזיכרון החופשי, בזמן שהמערכת כבר נמצאת בלחץ על הזיכרון.
  • SIGQUIT: מפיק תמונת מצב של הזיכרון ומסיים את התהליך כשמזוהים על ידי בדיקת האינסטרומנטציה.
  • TRIM_MEMORY_EVENTS: הקריאות החוזרות האלה זמינות ב-Android 4.1 (רמת API 16) ואילך, ומספקות פרטים התראות זיכרון לגבי התהליך שלכם.