ניתוח הזיכרון של Java

אפליקציות Java ו-Kotlin מנהלות את הזיכרון באמצעות heap שמתבצע בו איסוף אשפה. כשאובייקטים כבר לא נגישים, איסוף האשפה (GC) בסופו של דבר משחרר את המקום שלהם. דליפות זיכרון מתרחשות כשאובייקטים שכבר לא נחוצים עדיין מוחזקים על ידי 'שורשי GC', מה שמונע את השחזור שלהם.

מושגי ליבה

שורשי GC

GC Root הוא סוג מיוחד של אובייקט שאיסוף האשפה מתייחס אליו כאובייקט שתמיד אפשר להגיע אליו. דוגמאות:

  • שרשורים פעילים (ואובייקטים שההפניה אליהם מתבצעת מתוך מסגרות מחסנית Java שמופעלות כרגע).
  • Classes עם שיטות שפועלות באופן פעיל.
  • הפניות ל-JNI (הפניות גלובליות או מקומיות שמוחזקות על ידי קוד Native).

הנתיב לשורש של איסוף הזבל

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

הנתיב אל שורש ה-GC

עצי דומיננטיות

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

אומרים שאובייקט A שולט באובייקט B אם כל נתיב משורש GC כלשהו אל B חייב לעבור דרך A. אם A דומיננטי ב-B, אז שחזור של A יבטיח גם שניתן יהיה לשחזר את B, כי אין נתיבים אחרים משום שורש ל-B.

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

עץ דומיננטי

קבלת תמונות מצב של הזיכרון מ-Java

‫תמונת מצב של הזיכרון היא תמונת מצב של כל האובייקטים ב-Java heap בנקודת זמן מסוימת.

שימוש ב-ADB

כדי לתעד תמונת מצב של הזיכרון מתהליך שפועל, אפשר להעביר את שם החבילה ישירות אל am dumpheap. כדי להריץ את הפקודה הזו, צריך ליצור את האפליקציה באמצעות <profileable android:shell="true"/> או <debuggable>.

# 1. Trigger the dump (the command takes a moment to complete):
adb shell am dumpheap -g -b png com.android.memorylab /data/local/tmp/heap.hprof

# 2. Pull the file to your development machine:
adb pull /data/local/tmp/heap.hprof .

שימוש ב-Perfetto

בנוסף, אפשר להשתמש ב-Perfetto כדי לתעד את נתוני ה-heap של Java כחלק מתיעוד עקבות המערכת, על ידי הפעלת מקור הנתונים android.java_hprof בהגדרות של Perfetto. האפשרות הזו שימושית כדי ליצור קורלציה בין מצב הערימה לבין אירועים אחרים במערכת.

כדי ללכוד תמונת מצב של הזיכרון לאפליקציית MemoryLab באמצעות Perfetto, אפשר להשתמש בפקודה הבאה:

# Create a temp file for the configuration
cat > /tmp/java_heap.pbtx <<EOF
data_sources: {
    config {
        name: "android.java_hprof"
        java_hprof_config {
            process_cmdline: "com.android.memorylab"
        }
    }
}
EOF

# Run trace command referencing the file
external/perfetto/tools/record_android_trace -o java_heap.perfetto-trace \
  -t 10s -c /tmp/java_heap.pbtx

מידע נוסף זמין במאמר בנושא תמונות מצב של הזיכרון מ-Java ב-Perfetto.

ניתוח באמצעות AHAT

‫AHAT (Android Heap Analysis Tool) הוא הכלי המומלץ לצפייה בקובצי .hprof בדפדפן אינטרנט.

התחלת השימוש ב-AHAT

אם התקנתם את ahat בנתיב שלכם, מפעילים אותו באמצעות הפקודה:

ahat heap.hprof

או מריצים את קובץ ה-jar העצמאי:

java -jar ahat.jar heap.hprof

פותחים את הדפדפן בכתובת http://localhost:7100.

פרטים על קבלת AHAT או על פיתוח AHAT זמינים במאגר המקור של AHAT.

תהליכי עבודה מרכזיים לניתוח

איתור הדלפות

מחפשים את הכיתה של הפעילות (MainActivity) בתצוגה הקצאות.

תצוגה של AHAT שבה מוצגים מקרים

לוחצים על הכיתה כדי לראות את כל המופעים.

תצוגת AHAT שמציגה מקרים של MainActivity
לוחצים על המופע MainActivity כדי לבדוק אותו.

‫AHAT מציג פרטים של מופע

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

נתיב לדוגמה ב-AHAT משורש GC וגודל האובייקט

ניתוח מפות סיביות

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

תצוגה מקדימה של מפת סיביות ב-AHAT

דף הפעילות

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

  1. פעולה: ב-MemoryLab, מקישים על Leak an Activity (הדלפת פעילות). הפעולה הזו מפעילה את LeakedActivity, שיוצר דליפה בכוונה.
  2. ‫Dump: יצירת תמונת מצב של הזיכרון.
  3. ניתוח: לוחצים על Activity Leaks (דליפות של פעילות) בסרגל הצד של AHAT.
  4. אימות: הכלי AHAT יציג את com.android.memorylab.LeakedActivity כדליפה כי השדה mDestroyed שלו הוא true (מה שמציין שמחזור החיים של הפעילות הסתיים), אבל עדיין אפשר להגיע אליו משורש GC.

דף הדליפות של פעילות ב-AHAT

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

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

תרגיל: זיהוי דליפות באמצעות השוואה

  1. ערך בסיס: מפעילים את MemoryLab ויוצרים תמונת מצב של הזיכרון של ערך הבסיס:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/base.hprof
    adb pull /data/local/tmp/base.hprof .
    
  2. פעולה: מקישים על הקצאת זיכרון Java‏(10MB) כמה פעמים באפליקציה.

  3. השלב האחרון: יוצרים עוד dump של ה-heap:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/leaked.hprof
    adb pull /data/local/tmp/leaked.hprof .
    
  4. השוואה: מפעילים את AHAT עם קובץ ה-dump השני כקובץ הראשי והראשון כקובץ הבסיס:

    java -jar out/host/linux-x86/framework/ahat.jar leaked.hprof --baseline base.hprof
    
  5. סקירה כללית של הניתוח: הדף סקירה כללית כולל עכשיו עמודה של Δ (דלתא). תראו דלתא חיובית גדולה עבור ה-heap של app, שמצביעה על גידול משמעותי בזיכרון.

סקירה כללית של AHAT עם דלתא

  1. פירוט: לוחצים על rooted בתפריט. בדף הזה מוצגים אובייקטים שאפשר להגיע אליהם משורשי GC, במיון לפי הנפח שמתפנה (retained size). בחלק העליון יופיע MainActivity עם דלתא חיובית גדולה.

תצוגת AHAT עם הרשאות Root ודלתא

הקלטת דוחות קריסות של הקצאות

האפשרות Sample Path from GC Root מסבירה למה אובייקט עדיין פעיל, אבל לא מסבירה איך הוא נוצר. עקבות מחסנית של הקצאת זיכרון מספקים את שורת הקוד המדויקת שהקצתה אובייקט.

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

תרגיל: זיהוי המקור של מערכי בייטים

  1. מתחילים עם מעקב: עוצרים בכוח את MemoryLab ומפעילים אותו מחדש עם הדגל --track-allocation. כדי ללכוד יותר הקשר, מגדילים את עומק מחסנית ברירת המחדל.

    # Increase the allocation tracker's stack depth (requires a process restart)
    adb shell setprop dalvik.vm.allocTrackerMaxStack 16
    
    adb shell am force-stop com.android.memorylab
    adb shell am start --track-allocation -n com.android.memorylab/.MainActivity
    
  2. פעולה: מקישים כמה פעמים על הקצאת זיכרון Java‏(10MB).

  3. Dump: יצירת תמונת מצב של הזיכרון ושליפתה.

  4. ניתוח: פתיחת ה-dump ב-AHAT. עוברים למופע גדול של byte[]. (לדוגמה, בודקים את MainActivity ← mJavaAllocations (ArrayList) ← elementData (Object[]) ← רכיב המערך [0]).

  5. אימות: בתצוגת המופע, מעיינים בקטע אתר ההקצאה. יוצג דוח קריסות מלא שמוביל אל MainActivity.allocateJava.

אתר להקצאת AHAT

איתור מחרוזות כפולות וניפוח של מאזן הנוזלים

גם אם באפליקציה אין דליפות של GC-root קלאסיות, יכול להיות שזיכרון ה-heap הפעיל של Java יתנפח בגלל אלפי מופעים כפולים של java.lang.String שנוצרו במהלך ביטול הסדרות של JSON,‏ Protobuf,‏ Cursor או מסד נתונים של Room. מקשים חוזרים, מחרוזות סטטוס, תוויות קטגוריה או כתובות URL מוקצים מחדש לעיתים קרובות בכל תגובה לרשת או שאילתה במסד נתונים. במגוון רחב של אפליקציות של פידים, הודעות ותוכן, מחרוזות כפולות מהוות בדרך כלל 30% עד 60% מהזיכרון הפעיל. String

כדי לבדוק מחרוזות כפולות ב-AHAT:

  1. פותחים את הדף הקצאות ומסננים לפי java.lang.String.
  2. כשמשווים שני קובצי dump של ערימה באמצעות --baseline, בודקים אם ספירת המופעים java.lang.String והבייטים הכוללים גדלים באופן לא פרופורציונלי אחרי טעינת פיד או טעינת מטמון מקומי.
  3. מעיינים בטבלה של מופעי java.lang.String (ממוינת לפי גודל או ערך) כדי לזהות ערכי מחרוזת זהים שנשמרו בכמה אובייקטים של מודל בזיכרון.
  • פתרון: מומלץ להימנע מקריאה ל-String.intern() באופן לא מובחן על קלט שרירותי של משתמש או רשת, כי טבלת ה-intern של זמן הריצה היא גלובלית ויכולה לגרום למאבק על נעילה או לשמור מחרוזות למשך זמן ארוך מהנדרש. במקום זאת, כדאי לבטל כפילויות של מחרוזות דומיין בתדירות גבוהה במהלך ביטול הסדרות, באמצעות מטמון לביטול כפילויות עם היקף מוגבל (כמו LruCache<String, String> בתוך מנתח התגים או המתאם), או לייצג קבוצות קבועות של ערכים כסוגי נתונים מנויים או כקבועים מסוג integer.

ניתוח של דינמיקת הזיכרון ב-Java (פרופיל משולב)

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

נשתמש בהגדרה משולבת שמאפשרת:

  • Memory Counters (linux.process_stats): ספירת RSS ומדדי זיכרון אחרים.
  • ‫ATrace (קטגוריות dalvik, ‏ memory, ‏ sched): מתעד מצבי שרשור ואירועי GC.
  • ‫Heapprofd (android.heapprofd): מכוון גם ל-com.android.art (Java) וגם ל-libc.malloc (native) עם פריקות רציפות כל 5 שניות.

תרגיל: ניתוח משולב של הזיכרון

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

  1. Baseline: מצב בלי פעילות.
  2. ‫Java Churn: הקצאות זמניות שנאספות מיד כזבל.
  3. הקצאת Java מתמשכת: הקצאת אובייקטים של Java שנשארים בזיכרון.
  4. הקצאת מפת סיביות: הקצאה של נכסים גרפיים גדולים (שנמצאים בזיכרון ה-GPU או ב-heap המקורי).
  5. החזרה לשימוש: שחרור כל המשאבים שהוקצו.

1. הפעלה והכנה

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

    adb shell am force-stop com.android.memorylab
    adb shell am start -W -n com.android.memorylab/.MainActivity
    

2. התחלת מעקב והפעלת רצף

נתחיל מעקב למשך 40 שניות ונפעיל את אירועי הזיכרון באמצעות פקודות am broadcast.

  1. מתחילים את האיתור:

    adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/java_memory.perfetto-trace <<EOF
    buffers: {
        size_kb: 131072
        fill_policy: RING_BUFFER
    }
    data_sources: {
        config {
            name: "linux.process_stats"
            target_buffer: 0
            process_stats_config {
                scan_all_processes_on_start: true
                proc_stats_poll_ms: 100
            }
        }
    }
    data_sources: {
        config {
            name: "linux.ftrace"
            target_buffer: 0
            ftrace_config {
                ftrace_events: "sched/sched_switch"
                ftrace_events: "task/task_newtask"
                ftrace_events: "task/task_rename"
                ftrace_events: "ftrace/print"
                atrace_categories: "dalvik"
                atrace_categories: "am"
                atrace_categories: "res"
                atrace_categories: "memory"
                atrace_categories: "sched"
                atrace_apps: "com.android.memorylab"
            }
        }
    }
    data_sources: {
        config {
            name: "android.heapprofd"
            target_buffer: 0
            heapprofd_config {
                sampling_interval_bytes: 4096
                process_cmdline: "com.android.memorylab"
                heaps: "libc.malloc"
                heaps: "com.android.art"
                shmem_size_bytes: 8388608
                block_client: true
                continuous_dump_config {
                    dump_phase_ms: 1000
                    dump_interval_ms: 5000
                }
            }
        }
    }
    duration_ms: 40000
    EOF
    
  2. מפעילים את הרצף (מריצים את הפקודות האלה בטרמינל של המארח בזמן שהמעקב פועל, בהתאם לתזמון המוצע):

    # Wait ~5s for trace initialization, then start Java churn:
    adb shell am broadcast -a com.android.memorylab.CHURN_JAVA
    
    # Wait ~10s (at 15s mark), allocate 10MB of persistent Java memory:
    adb shell am broadcast -a com.android.memorylab.ALLOC_JAVA
    
    # Wait ~5s (at 20s mark), allocate 20MB of Bitmaps (native/graphics):
    adb shell am broadcast -a com.android.memorylab.LEAK_BITMAP
    
    # Wait ~10s (at 30s mark), free everything:
    adb shell am broadcast -a com.android.memorylab.FREE_ALL
    
  3. אפשרות חלופית (כלי CLI): אפשר גם להפעיל את הפרופיל באמצעות הסקריפט heap_profile ישירות, ולטרגט גם את Java וגם את ה-native heaps עם dump רציף:

    external/perfetto/tools/heap_profile -n com.android.memorylab \
      --heaps com.android.art,libc.malloc \
      -c 5000 \
      -d 40000 \
      -o java_memory_profile
    

3. ניתוח העקבות המשולב

פותחים את הנתונים שנאספו java_memory.perfetto-trace בממשק המשתמש של Perfetto.

טראקים מרכזיים ב-Perfetto

לפני שמנתחים את ציר הזמן, מאתרים את הרצועות החיוניות האלה בתהליך com.android.memorylab:

  1. ‫mem.rss.anon (Anonymous RSS): נמצא בקטע Memory של התהליך. במסלול הזה נמדד הזיכרון הפיזי (RAM) שהוקצה לתהליך על ידי מערכת ההפעלה. הוא מייצג את הזיכרון שבשימוש בפועל.
  2. ‫Heap size (KB): גם בקטע זיכרון. זהו מונה ספציפי ל-Dalvik/ART שמייצג את מרחב הכתובות הווירטואלי ששמור ל-Java heap. הוא משקף את מגבלת ה-heap הפנימית של המכונה הווירטואלית, שמשתנה כשמוקצים אובייקטים וכשמתבצעת פעולת איסוף נתונים.
  3. ‫HeapTaskDaemon: נמצא ברשימת השרשורים בתהליך. זהו ה-thread ברקע שבו מתבצע רוב העבודה של איסוף האשפה ב-ART. הפעילות כאן מציינת כרטיסים פעילים של GC.
  4. Continuous Allocation Dumps (heapprofd): מוצג כפרוסות צבעוניות לאורך ציר הזמן העליון. כל פלח מייצג משך זמן. אם לוחצים על פלח אחד או בוחרים טווח זמן, אפשר לבדוק את תרשים הלהבות (בחלונית התחתונה) כדי לראות מה הוקצה במהלך התקופה הזו, או com.android.art (הקצאות של Java) או libc.malloc (הקצאות של Native).

ניתוח שלבים כרונולוגי

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

שלב 1: ערך בסיס (0 שניות עד 5 שניות)
  • מה קורה: האפליקציה בלי פעילות וממתינה לפקודות.
  • סטטוס של טראק:
    • ‫mem.rss.anon: קו ישר בערך הבסיסי (בדרך כלל בסביבות 60-80MB, בהתאם למכשיר).
    • ‫Heap size (KB): קו ישר, שמתאים להקצאת זיכרון בערימה (heap allocation) הראשונית ב-Java.
    • ‫HeapTaskDaemon: בלי פעילות (לא מוצגות פרוסות של הרצה).
    • Allocation Dumps: הצגת הקצאות מינימליות של ערכי בסיס.

ממשק המשתמש של Perfetto שמציג את ערך הבסיס של שלב 1

שלב 2: תנודות בהקצאת זיכרון ב-Java‏ (5 שניות עד 15 שניות)
  • מה קורה: התהליך AllocationChurnThread מופעל, ומקצה שוב ושוב מערכים בגודל 1MB ואז מבטל את ההקצאה שלהם.
  • סטטוס של טראק:
    • ‫Heap size (KB): מוצג דפוס שיניים מהיר. גודל הערימה עולה ככל שההקצאות מצטברות, ויורד בחדות כשה-GC פועל.
    • ‫HeapTaskDaemon: מוצגת פעילות כמעט קבועה, עם פרוסות ביצוע שתואמות באופן מושלם לירידות בשיני המסור Heap size.
    • ‫mem.rss.anon: מעקב אחר פעילות הזיכרון מ-Java.
    • תמונות מצב של הקצאת זיכרון מ-Java: בחירת פרוסות במסלול הזה מציגה הקצאות של זיכרון מ-Java של com.android.art.

ממשק המשתמש של Perfetto שמציג נטישה בשלב 2 דוגמאות ההקצאה חושפות את AllocationChurnThread כהקצאה הראשית, כאשר כל ההקצאות חולקות את אותו callstack שמצביע על ה-lambda בתוך MainActivity.java.

ממשק המשתמש של Perfetto שמציג הקצאות של Java בשלב 2

שלב 3: הקצאת Java מתמשכת (15 שניות עד 20 שניות)
  • מה קורה: אנחנו מקצים 10MB של אובייקטים של Java ושומרים הפניה אליהם ב-mJavaAllocations.
  • סטטוס של טראק:
    • ‫Heap size (KB): קו הבסיס של שיני המסור עולה בכ-10MB.
    • ‫mem.rss.anon: העלייה היא בערך של 10MB, כי מערכת ההפעלה צריכה לגבות את ההקצאה המתמשכת הזו בדפים פיזיים חדשים.
    • תמונות מצב של הקצאת זיכרון מ-Java: בחירת פרוסות במסלול הזה מציגה הקצאות של זיכרון מ-Java של com.android.art.
    • קובצי dump של הקצאת זיכרון (תרשים להבה): בדיקה של ה-heap של com.android.art בקובץ ה-dump שנוצר בחלון הזה מראה נתיב הקצאה חדש מ-MainActivity.allocateJava שמשפיע על הגודל שנשמר.

ממשק המשתמש של Perfetto שמציג הקצאת Java מתמשכת בשלב 3 בוחרים דוגמה להקצאה שכוללת משך זמן שחופף להגדלה של 10MB להקצאה הקבועה. אפשר לראות שהקצאות הזיכרון מתפצלות לשני אתרים שונים. אתר אחד אחראי על מחזור החיים הקצר של הקצאת הזיכרון שראינו קודם, והאתר השני אחראי על הקצאת הזיכרון החדשה עם מחזור החיים הארוך.

ממשק המשתמש של Perfetto שמציג הקצאות של Java בשלב 3

שלב 4: הקצאת מפת סיביות (20 שניות עד 30 שניות)
  • מה קורה: אנחנו גם מקצים 20MB של מפות סיביות.
  • סטטוס של טראק:
    • ‫Heap size (KB): כמו קודם.
    • ‫mem.rss.anon: מוצג גידול משמעותי של כ-20MB, שמתאים להקצאות הנייטיב לנתוני הפיקסלים של Bitmap.
    • הקצאות (תרשים להבה): הפעם מתמקדים בפרוסות של libc.malloc (Native) heap.

ממשק המשתמש של Perfetto שמציג הקצאת Bitmap בשלב 4 מחסנית הקריאות (callstack) של הקצאת הזיכרון (allocation) ב-Native חושפת את הקצאת הזיכרון של Bitmap שמקורה בספריות גרפיקה ב-Native. זהו מקרה שימוש טוב למעקב אחר הקצאת זיכרון מקורי, כי לא תראו את הקצאות הזיכרון של מפת הסיביות האלה ב-Java heap.

ממשק המשתמש של Perfetto שמציג הקצאות מקומיות בשלב 4

שלב 5: שחזור (30 שניות עד 40 שניות)
  • מה קורה: אנחנו מפעילים את FREE_ALL, מנקים הפניות לכל ההקצאות המתמשכות של Java ולמפות הביטים, ואז מפעילים System.gc() באופן מפורש.
  • סטטוס של טראק:
    • ‫Heap size (KB): יורדת חזרה לרמת הבסיס.
    • ‫mem.rss.anon: יורד בחזרה, ומוצגות הדפים הפיזיים שמערכת ההפעלה משחררת.
    • ‫HeapTaskDaemon: מוצגת פעילות אחרונה כשהתהליך של איסוף האשפה מסתיים.

ממשק המשתמש של Perfetto שמציג את שלב 5: השבתה

מעקב אחר שגיאות היסטוריות של חוסר זיכרון (OOM) (ApplicationExitInfo)

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

ActivityManager am = getSystemService(ActivityManager.class);
List<ApplicationExitInfo> exitReasons = am.getHistoricalProcessExitReasons(null, 0, 1);
if (!exitReasons.isEmpty()) {
    ApplicationExitInfo info = exitReasons.get(0);
    if (info.getReason() == ApplicationExitInfo.REASON_LOW_MEMORY) {
        // App was killed by the system Low Memory Killer
    }
}

שיטות מומלצות

  1. הגדרת בסיס לפני הכול: תמיד צריך ליצור תמונת מצב של הזיכרון אחרי שהאפליקציה מאותחלת, אבל לפני שמבצעים את הפעולה שרוצים לבדוק.
  2. שימוש בדף Activity Leaks ב-AHAT: AHAT כולל דף ייעודי בשם Activity Leaks שמזהה באופן אוטומטי מקרים של פעילות שהושמדה אבל עדיין נשמרת בזיכרון. זו לרוב הדרך הכי מהירה למצוא דליפות נפוצות.
  3. בדיקת הנתיב אל שורשי ה-GC: לכל אובייקט שהודלף, משתמשים בתצוגה Path from Root ב-AHAT כדי להבין בדיוק איזו הפניה שומרת אותו בחיים (לדוגמה, שדה סטטי, שרשור שפועל לאורך זמן או מאזין רשום).

← כלים | ↑ למעלה | מפות ביטים →