מגביל זיכרון

‫Android 17 ואילך כולל את Memory Limiter, שירות מערכת שמנטר ומגביל את השימוש בזיכרון של תהליכי אפליקציות באמצעות Linux cgroup v2. התכונה 'הגבלת הזיכרון' מונעת מאפליקציות בודדות לצרוך זיכרון מערכת באופן לא פרופורציונלי, וכך מפחיתה את העומס הכולל על הזיכרון ומונעת הפסקות אגרסיביות של תהליכים קריטיים ברקע ובמטמון בגלל מחסור בזיכרון.

מנגנון

הכלי Memory Limiter משולב עם Activity Manager Service‏ (AMS) כדי לעקוב אחרי אירועים של מחזור חיים של תהליכים ושינויים במצב. הכלי Memory Limiter אוכף מגבלות זיכרון באמצעות ההיררכיה של cgroup v2 בליבת Linux.

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

memory.high
מגבלה רכה. אם חורגים מהמגבלה, התהליך מוגבל והליבה מנסה לבצע שחזור פרואקטיבי של דפים מהתהליך הזה.
memory.swap.max
מגביל את הכמות המקסימלית של מרחב החלפה (כמו ZRAM) שהתהליך יכול להשתמש בה.

ההשפעה על אפליקציות

אפליקציות שפועלות במסגרת מגבלות הזיכרון שלהן לא מושפעות מהתכונה Memory Limiter.

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

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

מעקב אחר תהליכים

כברירת מחדל, הכלי Memory Limiter עוקב אחרי תהליכי אפליקציות (UID >= 10000). תהליכי מערכת ליבה פטורים כדי לשמור על יציבות המערכת.

הכלי להגבלת הזיכרון מקצה מגבלות זיכרון על סמך מצב החשיפה של התהליך:

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

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

בטבלה הבאה מפורט מיפוי של מצבי תהליך ספציפיים לסיווגים של מגבלות זיכרון:

מצב התהליךמגבלת זיכרון
PERSISTENTבלתי מוגבלת
PERSISTENT_UIבלתי מוגבלת
TOPגלוי
BOUND_TOPגלוי
FOREGROUND_SERVICEלא מוצג
BOUND_FOREGROUND_SERVICEלא מוצג
IMPORTANT_FOREGROUNDגלוי
IMPORTANT_BACKGROUNDלא מוצג
TRANSIENT_BACKGROUNDלא מוצג
BACKUPלא מוצג
SERVICEלא מוצג
RECEIVERלא מוצג
TOP_SLEEPINGגלוי
HEAVY_WEIGHTלא מוצג
HOMEלא מוצג
LAST_ACTIVITYלא מוצג
CACHED_ACTIVITYמטמון
CACHED_ACTIVITY_CLIENTמטמון
CACHED_RECENTמטמון
CACHED_EMPTYמטמון

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

אם תהליך חורג ממגבלת memory.high שהוקצתה לו, הכלי Memory Limiter מזהה את האירוע ויכול להפעיל פעולות אבחון, כמו צילום פרופיל זיכרון או רישום של אנומליה ב-statsd.

הגדרות אישיות

ב-Android מגרסה 17 ואילך, מגדירים את Memory Limiter באמצעות קובץ התצורה הרגיל של הפלטפורמה במחיצת המערכת במיקום /system/etc/memory-limiter-config.xml.

פורמט XML

קובץ התצורה פועל לפי הסכימה שמוגדרת ב-memory-limiter-config.xsd. הקובץ מגדיר כמה קבוצות של מגבלות, שמסודרות לפי הזיכרון הזמין. השירות בוחר את קבוצת המגבלות התואמת הגבוהה ביותר על סמך זיכרון ה-RAM הזמין במכשיר. כל ערכי הזיכרון מצוינים במביבייט (MiB).

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM boundaries account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device [14336, 18432): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>18432</maximumAllowedMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device [9216, 14336): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>9216</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>14336</maximumAllowedMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device [6800, 9216): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>9216</maximumAllowedMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device [4800, 6800): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>6800</maximumAllowedMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device [3200, 4800): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3200</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>4800</maximumAllowedMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
מספר שלם חיובי שמזהה את גרסת ההגדרה. הערך חייב להיות 1.
minimumRequiredMemTotal

דרישות המערכת המינימליות MemTotal (ב-MiB, כולל רצפה) שנדרשות כדי שהמגבלה הזו תחול. השירות משווה את הנתון הזה לזיכרון הכולל שזמין ומדווח על ידי ליבת המערכת ב-/proc/meminfo.

הסבר על הקצאות זיכרון וגבולות: הערך של הליבה MemTotal משקף את ה-DRAM הפיזי שאפשר לגשת אליו ממערכת ההפעלה אחרי הפחתה של הקצאות זיכרון לחומרה ולתוכנת קושחה. הקצאות מיוחדות שומרות זיכרון RAM לרכיבי חומרה מיוחדים כמו GPU, מודם פס בסיס, ISP של מצלמה, סביבות מחשוב אמינות והיפר-ויזורים. בדרך כלל הן צורכות בין 500 MiB ליותר מ-2 GiB של זיכרון פיזי.

מכיוון שהערך של MemTotal נמוך מהערך של ה-DRAM הפיזי שמפורסם במפרט המכשיר, הסף של minimumRequiredMemTotal כולל מרווח לערכים האלה. לדוגמה, אם מגדירים טירגוט למכשירים עם נפח אחסון של 16GB, ערך הסף יהיה 14336 (14GB) ולא 16384 (16GB). אם מגדירים טירגוט למכשירים עם נפח אחסון של 12GB, ערך הסף יהיה 9216 (9GB). כך אפשר לוודא שכל מכשיר תואם לרמה המיועדת שלו, בלי קשר לשינויים ברמת הלוח.

maximumAllowedMemTotal

המערכת המקסימלית MemTotal (ב-MiB, לא כולל תקרה) שחלה על קבוצת המגבלות הזו. אם ערך המדד MemTotal גדול מהערך הזה או שווה לו, המגבלה שהוגדרה לא חלה. במכשירים עם 18,432 MiB ומעלה או במכשירים עם פחות מ-3,200 MiB, המגביל לא חל ולא מגביל את התהליכים.

memVisible

מגבלת הזיכרון הרכה (memory.high) ב-MiB שחלה על תהליכים גלויים.

memNotVisible

מגבלת הזיכרון הרכה (memory.high) ב-MiB חלה על תהליכים שלא נראים.

swapVisible

מגבלת מרחב ההחלפה (memory.swap.max) ב-MiB שחלה על תהליכים גלויים.

swapNotVisible

מגבלת אזור ההחלפה (memory.swap.max) ב-MiB שחלה על תהליכים לא גלויים.

מגבלות סטנדרטיות של הפלטפורמה

ב-Android 17 ואילך, הפלטפורמה מציינת מגבלות סטנדרטיות של זיכרון ושל קובץ החלפה ב-/system/etc/memory-limiter-config.xml, על פני רמות נפוצות של זיכרון RAM פיזי, באמצעות מרווחי טווח של [floor, ceiling):

זיכרון RAM פיזי לטירגוט טווח MemTotal [רצפה, תקרה)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
מגבלת זיכרון גלויה
(memVisible)
מגבלת זיכרון לא גלויה
(memNotVisible)
מגבלת החלפה גלויה
(swapVisible)
מגבלת החלפה לא גלויה
(swapNotVisible)
‫16 GB [14336, 18432) MiB ‫10,240 MiB‏ (10 GiB) ‫5,120 MiB‏ (5 GiB) ‫5,120 MiB‏ (5 GiB) ‫5,120 MiB‏ (5 GiB)
‫12 GB [9216, 14336) MiB ‫8,192 MiB ‏ (8 GiB) ‫4,096 MiB‏ (4 GiB) ‫4,096 MiB‏ (4 GiB) ‫4,096 MiB‏ (4 GiB)
‫8 GB [6800, 9216) MiB ‫5,120 MiB‏ (5 GiB) ‫3,072 MiB‏ (3 GiB) ‫3,072 MiB‏ (3 GiB) ‫3,072 MiB‏ (3 GiB)
‫6 GB [4800, 6800) MiB ‫4,096 MiB‏ (4 GiB) ‫2,048 MiB (2 GiB) ‫2,048 MiB (2 GiB) ‫2,048 MiB (2 GiB)
‫4 GB [3200, 4800) MiB ‫2,048 MiB (2 GiB) ‫1,024 MiB‏ (1 GiB) ‫1,024 MiB‏ (1 GiB) ‫1,024 MiB‏ (1 GiB)

עקרונות של מגבלת זיכרון

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

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

  • הקצאת משאבים יחסית: המגבלות של הפלטפורמה מכוילות בהתאם לקיבולת הזיכרון הפיזי (RAM):

    • תהליכים גלויים: הוקצו בערך 1/2 עד 2/3 מזיכרון ה-RAM הפיזי הכולל כדי לתמוך בעומסי עבודה פעילים של ממשק משתמש, קומפוזיציה ורינדור.
    • תהליכים לא גלויים: מוקצה בערך 1/4 עד 1/3 מזיכרון ה-RAM הפיזי הכולל למשימות ברקע.
  • החלה אוניברסלית: המגבלות חלות באופן אחיד על כל תהליכי האפליקציות במכשיר (UID‏ >= 10000), כולל אפליקציות מערכת ואפליקציות OEM שהותקנו מראש. הכלי Memory Limiter לא תומך ברשימות היתרים כדי להחריג אפליקציות ספציפיות, וכך הוא מבטיח ניהול הוגן של הזיכרון בכל האפליקציות.

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

  • החזרת ליבה ודינמיקה של החלפה: כשמתקרבים למגבלת memory.high של תהליך באפליקציה, ליבת Linux מכוונת את החזרת הזיכרון ספציפית לקבוצת הבקרה של התהליך הזה. הפעולות האלה כוללות השמטה של דפים לא פעילים שגובו בקובץ והחלפה של דפים אנונימיים לא פעילים ל-ZRAM, כדי לשמור על זרימה חלקה של המערכת בלי ליצור עומס על הזיכרון.

פקודות Shell

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

am memory-limiter <SUB-COMMAND>

status

בדוח של פקודת המשנה status מוצגים הסטטוס התפעולי והמדדים הפעילים של Memory Limiter:

adb shell am memory-limiter status

פלט לדוגמה:

Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36               watch-failed=0
  events=0                 processes=36             process-hwm=36

השדות העיקריים בפלט כוללים:

monitoring
מציין אם התהליכים נמצאים במעקב פעיל של Memory Limiter.
visibleMem וגם notVisibleMem
מגבלות הזיכרון המוחלטות המחושבות (memory.high) שנאכפות כרגע עבור כל מצב חשיפה.
visibleSwap וגם notVisibleSwap
המכסות המוחלטות של החלפות (memory.swap.max) שנאכפות כרגע לכל מצב חשיפה.
events
מספר הפעמים שתהליך חרג מהמגבלה שהוקצתה לו.
processes
המספר הנוכחי של תהליכים שנמצאים במעקב.

ignore

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

# Ignore a specific UID
adb shell am memory-limiter ignore 10087
# Ignore all processes (temporarily disables limiting)
adb shell am memory-limiter ignore all
# Resume normal limiting operation
adb shell am memory-limiter ignore none

ידני

פקודת המשנה manual מבטלת את המגבלות המחושבות לתהליך ספציפי לפי מזהה התהליך (PID) באמצעות ערך מוחלט מותאם אישית בבייט. הערך צריך להיות מספר שלם, אבל אפשר לכלול את הסיומת MB כדי לציין MiB או את הסיומת GB כדי לציין GiB:

# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1073741824
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1024MB
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1GB
# Remove the manual override for PID 1234
adb shell am memory-limiter manual 1234 none

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

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