تحليل ذاكرة Java

تُدير تطبيقات Java وKotlin الذاكرة من خلال كومة ذاكرة يتم جمع البيانات غير الضرورية منها. عندما يتعذّر الوصول إلى الكائنات، يسترد جامع البيانات المهملة (GC) المساحة التي تشغلها في النهاية. تحدث تسرّبات الذاكرة عندما تحتفظ "جذور جمع البيانات غير المرغوب فيها" بالكائنات التي لم تعُد مطلوبة، ما يمنع استردادها.

المفاهيم الأساسية

جذور تجميع البيانات المهمَلة

جذر جمع البيانات غير المرغوب فيها هو نوع خاص من الكائنات يتعامل معه جامع البيانات غير المرغوب فيها على أنّه يمكن الوصول إليه دائمًا. تشمل الأمثلة ما يلي:

  • سلاسل المحادثات النشطة (والعناصر التي تتم الإشارة إليها من إطارات حزمة Java التي يتم تنفيذها حاليًا).
  • الفئات التي يتم فيها تنفيذ الطرق بشكل نشط
  • مراجع JNI (مراجع عامة أو محلية يحتفظ بها الرمز البرمجي الأصلي)

المسار إلى جذر عملية جمع البيانات غير المستخدَمة

وطالما أنّ هناك سلسلة مراجع من GC Root إلى عنصر، يكون هذا العنصر "قابلاً للوصول" ولا يمكن جمع البيانات غير الضرورية منه. تُعرف هذه السلسلة باسم مسار جذر وحدة التحكّم بجمع البيانات غير الضرورية. لإصلاح مشكلة تسرُّب الذاكرة، عليك تحديد هذه السلسلة وقطعها.

المسار إلى جذر تجميع البيانات المهمَلة

أشجار المسيطر

في حين أنّ مسار جذر GC يوضّح سبب بقاء عنصر نشطًا، لا يوضّح مقدار الذاكرة التي يمكن استردادها في حال إيقاف هذا المرجع. لهذا الغرض، نستخدم أشجار المسيطر.

يقال إنّ العنصر A يسيطر على العنصر B إذا كان كل مسار من أي جذر GC إلى B يجب أن يمر عبر A. إذا كان أ يسيطر على ب، سيضمن استرداد أ أيضًا إمكانية استرداد ب، لأنّه لا توجد مسارات أخرى من أي جذر إلى ب.

يوضّح المخطّط البياني التالي رسمًا بيانيًا للعناصر وشجرة المسيطر المقابلة له. لاحظ كيف يمكن الوصول إلى العنصر D من خلال كل من A وB في الرسم البياني، وبالتالي لا يهيمن أي من A أو B على D، بل يكون GC Root هو العنصر المهيمن الأقرب إليه.

شجرة المسيطر

الحصول على لقطات لأجزاء من الذاكرة في Java

لقطة لأجزاء من الذاكرة هي لقطة لجميع العناصر في الذاكرة المؤقتة الخاصة بلغة Java في نقطة زمنية معيّنة.

استخدام 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 أيضًا تسجيل عمليات تفريغ الذاكرة المؤقتة في 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) هي الأداة المقترَحة لعرض ملفات .hprof في متصفّح الويب.

Starting AHAT

إذا كان لديك ahat مثبَّتًا في مسارك، شغِّله باستخدام:

ahat heap.hprof

أو شغِّل ملف jar المستقل:

java -jar ahat.jar heap.hprof

بعد ذلك، افتح المتصفّح على http://localhost:7100.

للحصول على تفاصيل حول الحصول على أداة AHAT أو إنشائها، يُرجى الاطّلاع على مستودع المصدر الخاص بأداة AHAT.

سير عمل التحليل الأساسي

العثور على تسريبات

ابحث عن فئة "النشاط" (MainActivity) في طريقة العرض عمليات التخصيص.

عرض &quot;أداة تحليل صحة التطبيق&quot; الذي يعرض الحالات

انقر على الصف للعثور على جميع المراجع.

عرض AHAT الذي يعرض مثيلات MainActivity
انقر على مثيل MainActivity لفحصه.

أداة AHAT تعرض تفاصيل مثيل

في عرض المثيل، يمكنك العثور على مسار العيّنة من جذر GC الذي يعرض سلسلة المراجع التي تمنع جمع البيانات غير الضرورية للكائن، وحجم الكائن الذي يعرض مقدار الذاكرة التي يحتفظ بها هذا المثيل المحدّد.

مسار عيّنة أداة تحليل الذاكرة التلقائي (AHAT) من جذر وحدة التحكّم في جمع البيانات المهملة وحجم العنصر

جارٍ تحليل الصور النقطية

توفّر أداة AHAT دعمًا خاصًا لعرض عناصر android.graphics.Bitmap، والتي تستهلك عادةً مقدارًا كبيرًا من الذاكرة. انقر على مثيل Bitmap للاطّلاع على معاينة معروضة لمحتواه.

AHAT Bitmap Preview

صفحة "تسريبات النشاط"

توفّر أداة AHAT طريقة عرض متخصّصة لتحديد الأنشطة التي تم تسريبها، وهي من أكثر عمليات تسريب الذاكرة شيوعًا وتأثيرًا في Android.

  1. الإجراء: في MemoryLab، انقر على تسريب نشاط. يؤدي ذلك إلى تشغيل LeakedActivity الذي يتسرّب عمدًا.
  2. تفريغ: يمكنك أخذ لقطة لأجزاء من الذاكرة.
  3. التحليل: انقر على تسريبات النشاط في الشريط الجانبي لـ "أداة تحليل أمان التطبيق" (AHAT).
  4. التحقّق: ستدرج أداة AHAT com.android.memorylab.LeakedActivity على أنّه تم تسريبه لأنّ الحقل mDestroyed الخاص به مضبوط على "صحيح" (ما يشير إلى انتهاء مراحل النشاط)، ولكن لا يزال من الممكن الوصول إليه من جذر جامع البيانات غير المستخدَمة.

صفحة &quot;تسريبات نشاط AHAT&quot;

مقارنة لقطات لأجزاء من الذاكرة

تُعد مقارنة ملفّين لتفريغ الذاكرة المؤقتة من أقوى الطرق لتحديد مشاكل الذاكرة. من خلال مقارنة تفريغ أساسي "نظيف" بتفريغ تم إجراؤه بعد تنفيذ بعض الإجراءات، يمكنك على الفور معرفة العناصر التي تم تجميعها.

التمرين: تحديد عمليات التسريب من خلال مقارنة الاختلافات

  1. الخط الأساسي: شغِّل MemoryLab وأنشئ تفريغًا أساسيًا لذاكرة التخزين المؤقت:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/base.hprof
    adb pull /data/local/tmp/base.hprof .
    
  2. الإجراء: انقر على تخصيص ذاكرة Java(‏10 ميغابايت) عدة مرات في التطبيق.

  3. الخطوة الأخيرة: الحصول على تفريغ كومة ثانٍ:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/leaked.hprof
    adb pull /data/local/tmp/leaked.hprof .
    
  4. المقارنة: ابدأ AHAT باستخدام التفريغ الثاني كعنصر أساسي والأول كمرجع:

    java -jar out/host/linux-x86/framework/ahat.jar leaked.hprof --baseline base.hprof
    
  5. نظرة عامة على "التحليل": تتضمّن صفحة نظرة عامة الآن عمود Δ (دلتا). سيظهر لك فرق كبير موجب في حزمة الذاكرة app، ما يشير إلى زيادة كبيرة في الذاكرة.

نظرة عامة على AHAT مع الفرق

  1. التفصيل: انقر على مُثبَّت في القائمة. تعرض هذه الصفحة الكائنات التي يمكن الوصول إليها من جذور جمع البيانات المهملة، ويتم ترتيبها حسب حجمها المحتفظ به. سيظهر MainActivity في أعلى الصفحة مع فرق إيجابي كبير.

عرض AHAT Rooted مع الفرق

تسجيل عمليات تتبُّع تسلسل استدعاء الدوال البرمجية لقيم التخصيص

في حين يوضّح لك مسار العيّنة من جذر جمع البيانات غير الضرورية سبب استمرار نشاط أحد العناصر، لا يوضّح لك كيفية إنشائه. توفّر عمليات تتبُّع تسلسل استدعاء الدوال البرمجية الخاصة بالتخصيص السطر الدقيق من الرمز البرمجي الذي خصّص عنصرًا.

المفهوم والمفاضلات: تسجيل تتبُّع تسلسل استدعاء الدوال البرمجية لكل عملية تخصيص ذاكرة مكلف من الناحية الحسابية ويستهلك قدرًا كبيرًا من الذاكرة. في تطبيق كبير الحجم، قد يؤدي ذلك إلى عدم إمكانية استخدام التطبيق تقريبًا. ومع ذلك، فإنّ 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(10 ميغابايت) عدة مرات.

  3. إنشاء تفريغ: يمكنك إنشاء تفريغ للذاكرة المكدّسة وسحبه.

  4. تحليل: لفتح ملف التفريغ في "أداة تحليل Heap" انتقِل إلى مثيل byte[] كبير. (مثلاً، فحص MainActivity → mJavaAllocations (ArrayList) → elementData (Object[]) → عنصر المصفوفة [0]).

  5. التحقّق: في عرض المثيل، اطّلِع على قسم موقع التخصيص. سيظهر لك تتبُّع تسلسل استدعاء الدوال البرمجية الكامل الذي يؤدي إلى MainActivity.allocateJava.

AHAT Allocation Site

البحث عن السلاسل المكرّرة وتضخّم البيانات

حتى عندما لا يتضمّن التطبيق أي تسريبات كلاسيكية لجذور جمع البيانات غير الضرورية، يمكن أن يتضخّم حجم الذاكرة المؤقتة النشطة في Java بسبب آلاف النسخ المكرّرة من مثيلات java.lang.String التي تم إنشاؤها أثناء إلغاء تسلسل JSON أو Protobuf أو Cursor أو قاعدة بيانات Room. غالبًا ما يتم تخصيص المفاتيح المتكررة أو سلاسل الحالة أو تصنيفات الفئات أو عناوين URL من جديد في كل استجابة من الشبكة أو طلب بحث من قاعدة البيانات. في التطبيقات الكبيرة التي تتضمّن خلاصات ومراسلة ومحتوى، تمثّل السلاسل المكرّرة عادةً من% 30 إلى% 60 من Stringالذاكرة النشطة.

لفحص السلاسل المكرّرة في "أداة تحليل تطبيقات Android"، اتّبِع الخطوات التالية:

  1. افتح صفحة عمليات التخصيص وفلتر حسب java.lang.String.
  2. عند مقارنة ملفَي تفريغ للذاكرة المؤقتة باستخدام --baseline، تحقَّق مما إذا كانت أعداد مثيلات java.lang.String وإجمالي وحدات البايت تزداد بشكل غير متناسب بعد إعادة تعبئة خلاصة أو تحميل ذاكرة تخزين مؤقت محلية.
  3. تصفَّح جدول مثيلات java.lang.String (مرتّبًا حسب الحجم أو القيمة) لتحديد قيم السلسلة المتطابقة التي يتم الاحتفاظ بها في عدّة عناصر نموذجية في الذاكرة.
  • الحلّ: تجنَّب استدعاء String.intern() بشكل عشوائي عند تلقّي أي إدخال من المستخدم أو الشبكة، لأنّ جدول السلسلة الداخلية لوقت التشغيل هو جدول عام ويمكن أن يؤدي إلى حدوث تعارض في القفل أو الاحتفاظ بالسلاسل لفترة أطول من اللازم. بدلاً من ذلك، يمكنك إزالة التكرار من سلاسل النطاقات المتكرّرة بشكل كبير أثناء إلغاء التسلسل باستخدام ذاكرة تخزين مؤقت محدودة النطاق لإزالة التكرار (مثل LruCache<String, String> داخل المحلّل أو المحوّل)، أو يمكنك تمثيل مجموعات ثابتة من القيم كقيم تعدادية أو ثوابت عددية.

تحليل ديناميكيات ذاكرة Java (الملف الشخصي المجمّع)

للحصول على صورة كاملة عن سلوك الذاكرة في أحد التطبيقات، يمكنك دمج عدّادات الذاكرة ونشاط سلسلة التعليمات وتحديد الملفات الشخصية لتخصيص الذاكرة استنادًا إلى حزمة استدعاء الدوال البرمجية في عملية تتبُّع واحدة من Perfetto. يتيح لك ذلك ربط مقاييس الذاكرة على مستوى النظام (مثل حجم مجموعة البيانات المقيمة وحجم الذاكرة المؤقتة) بعمليات تنفيذ الرموز البرمجية ومواقع التخصيص المحدّدة.

سنستخدم إعدادات مجمَّعة تتيح ما يلي:

  • عدادات الذاكرة (linux.process_stats): تستطلع هذه العدادات مقياس RSS ومقاييس الذاكرة الأخرى.
  • ATrace (الفئات dalvik وmemory وsched): تسجّل هذه الأداة حالات سلاسل التنفيذ وأحداث جمع البيانات غير الضرورية.
  • Heapprofd (android.heapprofd): يستهدف كلاً من الذاكرة المؤقتة com.android.art (Java) والذاكرة المؤقتة libc.malloc (الرمز البرمجي الأصلي) من خلال عمليات تفريغ مستمرة كل 5 ثوانٍ.

تمرين: تحليل الذاكرة المجمّعة

في هذا التمرين، سنشغّل تطبيق MemoryLab وننفّذ سلسلة من عمليات الذاكرة لمراقبة الأنماط المختلفة في التتبُّع:

  1. المرجع: حالة غير نشطة
  2. Java Churn: عمليات تخصيص مؤقتة يتم جمعها على الفور.
  3. تخصيص Java المستمر: تخصيص عناصر Java التي تظل في الذاكرة
  4. تخصيص الصور النقطية: تخصيص مواد عرض رسومات كبيرة (تكون في الذاكرة المجمّعة الأصلية/ذاكرة الرسومات).
  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. طريقة بديلة (أداة سطر الأوامر): يمكنك أيضًا بدء إنشاء الملف الشخصي باستخدام النص البرمجي heap_profile مباشرةً، مع استهداف كل من ذاكرة Java وذاكرة التطبيق الأصلية من خلال عمليات تفريغ مستمرة:

    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 UI.

المقاطع الرئيسية في Perfetto

قبل تحليل المخطط الزمني، حدِّد المواضع التالية في عملية com.android.memorylab:

  1. mem.rss.anon (ذاكرة RSS مجهولة الهوية): يمكن العثور عليها ضمن قسم الذاكرة الخاص بالعملية. يتتبّع هذا المقياس مقدار الذاكرة الفعلية (RAM) التي خصّصها نظام التشغيل للعملية. يمثّل ذلك استهلاك الذاكرة الفعلي.
  2. Heap size (KB): يظهر أيضًا ضمن قسم الذاكرة. هذا عدّاد خاص بـ Dalvik/ART يمثّل مساحة العناوين الافتراضية المحجوزة لمجموعة بيانات Java. وهو يعكس الحد الأقصى الداخلي لذاكرة التجميع في الجهاز الافتراضي، والذي يتغير مع تخصيص الكائنات وتشغيل عملية جمع البيانات غير الضرورية.
  3. HeapTaskDaemon: يمكن العثور عليها في قائمة سلاسل المحادثات ضمن العملية. هذه هي سلسلة التعليمات البرمجية التي تعمل في الخلفية والتي ينفّذ فيها جامع البيانات المهملة في ART معظم عمله. يشير النشاط هنا إلى بطاقات GC نشطة.
  4. عمليات تفريغ التخصيص المستمر (heapprofd): تظهر على شكل شرائح ملوّنة على طول المخطط الزمني العلوي. يمثّل كل جزء مدة زمنية. يتيح لك النقر على شريحة واحدة أو اختيار نطاق زمني فحص مخطط اللهب (في اللوحة السفلية) لكل من com.android.art (عمليات تخصيص Java) أو libc.malloc (عمليات التخصيص الأصلية) لمعرفة ما تم تخصيصه خلال تلك الفترة.

تحليل المراحل الزمنية

لنراجع التتبُّع زمنيًا لمعرفة كيفية تفاعل هذه المسارات خلال كل مرحلة من مراحل التمرين.

المرحلة 1: المستوى الأساسي (من 0 إلى 5 ثوانٍ)
  • ما يحدث: يكون التطبيق غير نشط وينتظر تلقّي الأوامر.
  • حالة الأغنية:
    • mem.rss.anon: خط مستوٍ عند خط الأساس (عادةً ما يكون حوالي 60 إلى 80 ميغابايت حسب الجهاز).
    • Heap size (KB): خط مستقيم يطابق عملية تخصيص الذاكرة الأولية في Java
    • HeapTaskDaemon: في وضع الخمول (لا يتم عرض أي شرائح).
    • عمليات تفريغ التخصيص: تعرض هذه العمليات الحد الأدنى من عمليات التخصيص الأساسية.

واجهة مستخدم Perfetto تعرض البيانات الأساسية للمرحلة 1

المرحلة 2: معدّل التغيّر في تخصيص Java (من 5 ثوانٍ إلى 15 ثانية)
  • ما يحدث: يتم بدء AllocationChurnThread، ويتم بشكل متكرر تخصيص مصفوفات بحجم 1 ميغابايت ثم تجاهلها.
  • حالة الأغنية:
    • Heap size (KB): يعرض نمط أسنان المنشار السريع. يزداد حجم الذاكرة المؤقتة مع تراكم عمليات التخصيص، وينخفض بشكل حاد عند تنفيذ عملية جمع البيانات غير الضرورية.
    • HeapTaskDaemon: يعرض نشاطًا شبه ثابت، مع توافق شرائح التنفيذ تمامًا مع الانخفاضات في شكل المنشار Heap size.
    • ‫mem.rss.anon: يتتبّع نشاط الذاكرة المخصّصة في Java.
    • لقطات لأجزاء من الذاكرة المخصّصة في Java: يؤدي اختيار شرائح في هذا المخطّط إلى عرض عمليات تخصيص الذاكرة في com.android.art.

واجهة مستخدم Perfetto تعرض معدّل توقّف الاستخدام في المرحلة 2 تكشف عيّنات التخصيص أنّ AllocationChurnThread هو المخصّص الأساسي، مع مشاركة جميع عمليات التخصيص في حزمة استدعاء الدوال نفسها التي تشير إلى دالة lambda داخل MainActivity.java.

واجهة مستخدم Perfetto تعرض عمليات تخصيص Java في المرحلة 2

المرحلة 3: تخصيص Java المستمر (من 15 إلى 20 ثانية)
  • ما يحدث: نخصّص 10 ميغابايت من عناصر Java ونحتفظ بمرجع لها في mJavaAllocations.
  • حالة الأغنية:
    • Heap size (KB): يرتفع خط الأساس لنمط الأسنان المنشارية بمقدار 10 ميغابايت تقريبًا.
    • ‫mem.rss.anon: تزداد بمقدار 10 ميغابايت تقريبًا، لأنّ نظام التشغيل يجب أن يتيح هذا التخصيص الدائم باستخدام صفحات فعلية جديدة.
    • لقطات لأجزاء من الذاكرة المخصّصة في Java: يؤدي اختيار شرائح في هذا المخطّط إلى عرض عمليات تخصيص الذاكرة في com.android.art.
    • عمليات تفريغ التخصيص (مخطط اللهب): عند فحص الذاكرة المؤقتة com.android.art لعملية التفريغ التي تم إجراؤها في هذه النافذة، يظهر مسار تخصيص جديد من MainActivity.allocateJava يساهم في الحجم المحتفظ به.

واجهة مستخدم Perfetto تعرض تخصيص Java مستمرًا في المرحلة 3 اختَر نموذجًا للتخصيص يغطي مدة تتداخل مع الزيادة البالغة 10 ميغابايت للتخصيص الدائم. من المفترض أن ترى اختلافًا في حِزم استدعاءات التخصيص، حيث تتضمّن حزمة استدعاءات الموقع الإلكتروني تخصيصًا قصير الأمد مشابهًا لما رأيناه سابقًا، بينما تتضمّن حزمة استدعاءات الموقع الإلكتروني الأخرى تخصيصًا جديدًا طويل الأمد.

واجهة مستخدم Perfetto تعرض عمليات تخصيص Java في المرحلة 3

المرحلة 4: تخصيص الصورة النقطية (من 20 إلى 30 ثانية)
  • ما يحدث: نخصّص أيضًا 20 ميغابايت من الصور النقطية.
  • حالة الأغنية:
    • Heap size (KB): كما كان من قبل
    • mem.rss.anon: يعرض زيادة كبيرة بمقدار 20 ميغابايت تقريبًا، وهي تتوافق مع عمليات التخصيص الأصلية لبيانات وحدات البكسل الخاصة بالصورة النقطية.
    • عمليات تفريغ التخصيص (مخطط اللهب): في هذا الوقت، ركِّز على شرائح الذاكرة المؤقتة libc.malloc (الرمز البرمجي الأصلي).

واجهة مستخدم Perfetto تعرض عملية تخصيص Bitmap في المرحلة 4 تكشف عمليات تتبُّع تسلسل استدعاء الدوال البرمجية الخاصة بتخصيص الذاكرة الأصلية عن تخصيص Bitmap الذي مصدره مكتبات الرسومات الأصلية. وهذه حالة استخدام جيدة لتتبُّع عمليات التخصيص الأصلية، لأنّك لن ترى عمليات تخصيص Bitmap هذه في مساحة التخزين المؤقت في Java.

واجهة مستخدم Perfetto تعرض عمليات تخصيص الذاكرة الأصلية في المرحلة 4

المرحلة 5: الاستصلاح (الثلاثينات والأربعينات)
  • ما يحدث: يتم تشغيل FREE_ALL، ما يؤدي إلى محو مراجع جميع عمليات تخصيص Java الثابتة والصور النقطية، ثم يتم تشغيل System.gc() بشكل صريح.
  • حالة الأغنية:
    • ‫Heap size (KB): ينخفض مستوى الأداء إلى المستوى الأساسي.
    • ‫mem.rss.anon: ينخفض هذا المقياس مجددًا، ما يشير إلى أنّ نظام التشغيل يستعيد الصفحات الفعلية.
    • ‫HeapTaskDaemon: يعرض هذا الرسم البياني ارتفاعًا نهائيًا في النشاط أثناء معالجة عملية جمع البيانات المُهمَلة.

واجهة مستخدم Perfetto تعرض عملية استرداد المرحلة 5

رصد أخطاء نفاد الذاكرة (OOM) السابقة (ApplicationExitInfo)

يُعدّ رصد عُطل LMK أثناء حدوثه أمرًا رائعًا لتصحيح الأخطاء النشط، ولكن بالنسبة إلى قياس استخدام التطبيق عن بُعد، يمكنك استخدام واجهة برمجة التطبيقات ApplicationExitInfo. يتيح ذلك لتطبيقك معرفة سبب إيقافه في جلسة سابقة.

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. استخدام صفحة "تسريب النشاط" في أداة AHAT: تتضمّن أداة AHAT صفحة مخصّصة لتسريب النشاط تحدّد تلقائيًا مثيلات النشاط التي تم إيقافها ولكنها لا تزال محفوظة في الذاكرة. وهذه الطريقة هي غالبًا الأسرع للعثور على عمليات التسريب الشائعة.
  3. التحقّق من مسار جذور GC: بالنسبة إلى أي عنصر تم تسريبه، استخدِم طريقة العرض المسار من الجذر في أداة AHAT لمعرفة المرجع الذي يمنع إزالة العنصر من الذاكرة (على سبيل المثال، حقل ثابت أو سلسلة محادثات طويلة الأمد أو أداة معالجة مسجّلة).

← الأدوات | ↑ للأعلى | صور نقطية →