ב-Android מגרסה 17 ואילך יש את Memory Limiter, שירות מערכת שעוקב אחרי השימוש בזיכרון של תהליכי אפליקציות ומגביל אותו באמצעות Linux cgroup v2. התכונה "הגבלת הזיכרון" מונעת מאפליקציות בודדות לצרוך כמות לא פרופורציונלית של זיכרון המערכת. כך היא מפחיתה את העומס הכולל על הזיכרון ומונעת הפסקות אגרסיביות של תהליכים קריטיים בחזית ובתהליכים שנשמרו במטמון בגלל מחסור בזיכרון.
מנגנון
התכונה "מגביל הזיכרון" משתלבת עם Activity Manager Service (AMS) כדי לעקוב אחרי אירועים במחזור החיים של התהליך ושינויים בסטטוס. התכונה "מגבלת זיכרון" אוכפת את מגבלות הזיכרון באמצעות היררכיית cgroup v2 של ליבת Linux.
כדי לתמוך במגביל הזיכרון, הליבה של המכשיר צריכה להפעיל את cgroup v2 ואת הבקר memory. השירות מסתמך באופן ספציפי על המאפיינים הבאים:
memory.high- מגבלה גמישה. אם חורגים מהמגבלה, התהליך מוגבל והליבה מנסה להחזיר דפים באופן יזום מהתהליך הזה.
memory.swap.max- מגבילה את הכמות המקסימלית של שטח ההחלפה (כמו ZRAM) שהתהליך יכול להשתמש בו.
ההשפעה על האפליקציות
אפליקציות שפועלות במסגרת מגבלות הזיכרון שלהן לא מושפעות מהמגבלה.
כשאפליקציה חורגת ממגבלת ה-memory.high, הליבה מפנה את הזיכרון הנקי של האפליקציה שמגובה בקבצים, ומחליפה את הזיכרון האנונימי הלא פעיל שלה כדי שהאפליקציה לא תחרוג מהמגבלה. כתוצאה מפעילות הפינוי וההחלפה של הדפים, יכול להיות שהאפליקציה תעבור ויסות זמני של הביצוע, אבל היא תמשיך לפעול.
במצבים קיצוניים, כמו דליפת זיכרון לא מבוקרת שבה האפליקציה ממשיכה להקצות זיכרון אנונימי אחרי ששטח ההחלפה נגמר, האפליקציה לא מצליחה להקצות זיכרון ונסגרת.
מעקב אחרי תהליכים
התכונה "הגבלת הזיכרון" מחילה את מגבלות הפלטפורמה שבדף הזה על תהליכי האפליקציה (UID >= 10000). תהליכים עם מזהה משתמש (UID) נמוך מ-10,000 לא מקבלים מגבלות פלטפורמה, וגם לא החבילה של שירות ההסקה שמוגדר כברירת מחדל בארגז החול במכשיר (config_defaultOnDeviceSandboxedInferenceService). אפליקציות ושירותים של המערכת עדיין יכולים להצהיר על תקציבי זיכרון מצומצמים יותר, כפי שמתואר בקטע תקציבי זיכרון של המערכת.
התכונה "מגביל הזיכרון" מקצה מגבלות זיכרון על סמך מצב החשיפה של התהליך:
תהליכים גלויים: תהליכים שמארחים ממשק משתמש שגלוי למשתמש (כמו הפעילות שנמצאת כרגע במוקד או אינטראקציה פעילה). כדי להציג ממשק משתמש נדרשת קבוצת עבודה גדולה יותר לעיבוד ולצינורות עיבוד גרפיים, ולכן תהליכים גלויים מקבלים מגבלת זיכרון נדיבה יותר.
תהליכים לא גלויים: תהליכים שפועלים ברקע בלי להציג ממשק משתמש (כמו שירותים שפועלים ברקע או מקלטי שידור). הם לא מעבדים רכיבי ממשק משתמש, ולכן הם פועלים תחת מגבלה מחמירה יותר.
בטבלה הבאה מפורטים מצבי תהליך ספציפיים וסיווגים של מגבלות הזיכרון בפלטפורמה:
| מצב התהליך | מגבלת הזיכרון |
|---|---|
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 | מטמון |
התהליכים PERSISTENT ו-PERSISTENT_UI לא מקבלים מגבלה ברמת הפלטפורמה. אם אחד מהתהליכים האלה מצהיר על תקציב זיכרון, חל עליו תקציב החזית, כפי שמתואר בקטע קטגוריות של תקציב זיכרון.
במצב השמירה במטמון, התהליכים מוקפאים ואז מוחזרים למצב הקודם שלהם ככל האפשר.
כשתהליך חורג מהמגבלה שהוקצתה לו ב-memory.high, הכלי Memory Limiter מזהה את האירוע ויכול להפעיל פעולות אבחון, כמו תיעוד פרופיל הזיכרון או רישום חריגה ב-statsd.
קטגוריות של תקציב הזיכרון
במכשירים שתומכים במכסות זיכרון, אפליקציות ושירותי מערכת יכולים להצהיר על מכסות מחמירות יותר מהמגבלות של הפלטפורמה. האפליקציות מצהירות על תקציבים בקובץ המניפסט שלהן, כמו שמתואר בקטע הגדרת תקציבי זיכרון לאפליקציות.
לאפליקציות ולשירותים של המערכת, ראו תקציבי זיכרון של המערכת. התקציב מגדיר את memory.high, והוא לא יכול לחרוג מהמגבלה של הפלטפורמה לאותו מצב תהליך.
כשלתהליך יש תקציב, מגביל הזיכרון ממפה את מצב התהליך לאחת מארבע קטגוריות. הערכים android:state, foreground, perceptible ו-background במניפסט בוחרים את התקציב של הקטגוריה התואמת.
לתהליכים בקטגוריה שנשמרה במטמון אין תקציב פעיל.
| מצב התהליך | טווח תקציב |
|---|---|
PERSISTENT | חזית |
PERSISTENT_UI | חזית |
TOP | חזית |
BOUND_TOP | חזית |
TOP_SLEEPING | חזית |
FOREGROUND_SERVICE | ניתן לתפיסה |
BOUND_FOREGROUND_SERVICE | ניתן לתפיסה |
IMPORTANT_FOREGROUND | ניתן לתפיסה |
IMPORTANT_BACKGROUND | ניתן לתפיסה |
TRANSIENT_BACKGROUND | רקע |
BACKUP | רקע |
SERVICE | רקע |
RECEIVER | רקע |
HEAVY_WEIGHT | רקע |
HOME | רקע |
LAST_ACTIVITY | רקע |
CACHED_ACTIVITY | מטמון |
CACHED_ACTIVITY_CLIENT | מטמון |
CACHED_RECENT | מטמון |
CACHED_EMPTY | מטמון |
הגדרות אישיות
ב-Android מגרסה 17 ואילך, מגדירים את מגביל הזיכרון באמצעות קובץ התצורה הרגיל של הפלטפורמה במחיצת המערכת ב-/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(14GiB) במקום16384(16GiB), וברמה של מכשירים עם 12GB מצוין סף של9216(9GiB). כך אפשר לוודא שכל מכשיר תואם לרמה המיועדת שלו, בלי קשר לשינויים בהקצאה ברמת הלוח.maximumAllowedMemTotalהערך המקסימלי של
MemTotalבמערכת (ב-MiB, תקרה בלעדית) שעליו חלה המגבלה הזו. אם הערך שלMemTotalגדול מהתקרה הזו או שווה לה, המגבלה שהוגדרה לא חלה. במכשירים עם זיכרון של 18,432MiB או יותר, או במכשירים עם זיכרון של פחות מ-3,200MiB, מגביל הזיכרון לא מחיל מגבלות, והתהליכים לא מוגבלים.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) |
Not visible swap limit (swapNotVisible) |
|---|---|---|---|---|---|
| 16GB | [14336, 18432) MiB |
10,240 MiB (10 GiB) | 5,120 MiB (5 GiB) | 5,120 MiB (5 GiB) | 5,120 MiB (5 GiB) |
| 12GB | [9216, 14336) MiB |
8,192 MiB (8 GiB) | 4,096 MiB (4 GiB) | 4,096 MiB (4 GiB) | 4,096 MiB (4 GiB) |
| 8GB | [6800, 9216) MiB |
5,120 MiB (5 GiB) | 3,072 MiB (3 GiB) | 3,072 MiB (3 GiB) | 3,072 MiB (3 GiB) |
| 6GB | [4800, 6800) MiB |
4,096 MiB (4 GiB) | 2,048 MiB (2 GiB) | 2,048 MiB (2 GiB) | 2,048 MiB (2 GiB) |
| 4GB | [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:
- תהליכים גלויים: מוקצים בערך חצי עד שני שלישים מסך זיכרון ה-RAM הפיזי כדי לתמוך בעומסי עבודה פעילים של ממשק משתמש, קומפוזיציה ורינדור.
- תהליכים לא גלויים: מוקצה בערך רבע עד שליש מסך כל זיכרון ה-RAM הפיזי למשימות ברקע.
תחולה אוניברסלית: המגבלות חלות באופן אחיד על כל תהליכי האפליקציות במכשיר (UID >= 10000), כולל אפליקציות מערכת ואפליקציות OEM שמותקנות מראש. מלבד שירות ההסקה שמופעל כברירת מחדל בארגז חול במכשיר, הכלי להגבלת הזיכרון לא מחריג אפליקציות ספציפיות, כדי להבטיח ניהול הוגן של הזיכרון בכל האפליקציות. כדי להגדיר רמות הגנה מינימליות של
memory.minבכוונון עצמי על תהליכי רקע אינטראקטיביים קריטיים, ורמות מקסימליות הדוקות יותר שלmemory.highשמכוילות לעומסי עבודה באפליקציות ובשירותים ספציפיים של המערכת, קראו את המאמר תקציבי זיכרון המערכת.היעדר ממשקי API לשאילתות בזמן ריצה: ב-Android בגרסה 17 ואילך, אפליקציות לא יכולות לשלוח שאילתות באופן פרוגרמטי לגבי מגבלות הזיכרון שהוקצו להן בזמן הריצה. המגבלות הרגילות של הפלטפורמה מוגדרות עם מרווחים נדיבים, כך שאפליקציות שפועלות בצורה תקינה פועלות ללא מגבלות במהלך שימוש רגיל.
החזרה והחלפה של דינמיקה של ליבה: כשמגבלת ה-
memory.highשל תהליך באפליקציה מתקרבת, ליבת Linux מפנה את החזרת הזיכרון באופן ספציפי ל-cgroup של התהליך הזה. זה כולל הסרה של דפים לא פעילים שמגובים בקבצים והחלפה של דפים אנונימיים לא פעילים ל-ZRAM, כדי לשמור על פעולה חלקה של המערכת בלי להפעיל עומס על הזיכרון הגלובלי.
פקודות מעטפת
הפקודה am memory-limiter מאפשרת למפתחים ולמפעילי מערכות ליצור אינטראקציה עם השירות בזמן הריצה לצורך פיתוח, בדיקה וניפוי באגים:
am memory-limiter <SUB-COMMAND>status
פקודת המשנה status מדווחת על סטטוס הפעילות ועל המדדים הפעילים של מגביל הזיכרון:
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- מציין אם הכלי להגבלת הזיכרון עוקב באופן פעיל אחרי תהליכים.
visibleMemוגםnotVisibleMem- מגבלות הזיכרון המוחלטות המחושבות (
memory.high) שנאכפות כרגע לכל מצב חשיפה. visibleSwapוגםnotVisibleSwap- מגבלות ההחלפה המוחלטות המחושבות (
memory.swap.max) שנאכפות כרגע לכל מצב חשיפה. events- מספר הפעמים שתהליך חרג מהמגבלה שהוקצתה לו.
processes- המספר הנוכחי של התהליכים במעקב.
התעלמות
פקודת המשנה ignore מחריגה באופן זמני מזהה משתמש ספציפי או את כל התהליכים מהגבלת הזיכרון. האפשרות הזו שימושית להשוואת ביצועים, לבדיקת עומסים או לאבחון התנהגות הזיכרון:
# 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
שינויים ידניים חלים רק על משך החיים של המופע הספציפי של התהליך. אם התהליך יתחיל מחדש, הוא יחזור למגבלות הרגילות בהתאם למצב שלו.
אי אפשר להגדיר ידנית ערך שחורג מהמגבלות הפיזיות של המערכת.