أدوات التقييم السريع

عندما تحتاج إلى التحقيق في استخدام الذاكرة، ابدأ بأدوات التقييم عالية المستوى لتحديد فئات الذاكرة الأكثر أهمية.

dumpsys meminfo

الطريقة الأكثر شيوعًا للحصول على نظرة عامة على استخدام الذاكرة في التطبيق هي dumpsys meminfo. للاطّلاع على تفاصيل دقيقة تشمل جميع عمليات ربط الذاكرة، من المفيد غالبًا تضمين العلامة -a:

adb shell dumpsys meminfo -a <package_name_or_pid>

في ما يلي نموذج لنتائج تطبيق تحت ضغط الذاكرة (لاحظ ارتفاع SwapPss):

** MEMINFO in pid 4562 [com.android.memorylab] **
                   Pss  Private  Private  SwapPss      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap      394      268       64    17440     4264    29416    18128     8040
  Dalvik Heap      165        8        0      672     8196    40949    32757     8192
 Dalvik Other       47       28        0      604     1132
        Stack      112      108        4      312      120
...
        TOTAL    33197      580      788    21920   167776    70365    50885    16232

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:       12                          32608
         Native Heap:      268                           4264
                Code:      724                         125672
               Stack:      108                            120
            Graphics:        0                              0
       Private Other:      256
              System:    31829
             Unknown:                                    5112

           TOTAL PSS:    33197            TOTAL RSS:   167776       TOTAL SWAP PSS:    21920

 Objects
               Views:       15         ViewRootImpl:        1
         AppContexts:        5           Activities:        1
...

تفسير البيانات

  • إجمالي حجم المجموعة المتناسب: يشير إلى حجم المجموعة المتناسب. هذا هو مجموع Private Dirty + Private Clean + الحصة العادلة للعملية من الذاكرة المشتركة مع العمليات الأخرى (مثل صورة تشغيل Zygote). هذا هو أفضل مقياس لمعرفة "مقدار الذاكرة التي يستهلكها هذا التطبيق".
  • الذاكرة الخاصة المعدَّلة: هي ذاكرة الوصول العشوائي التي تستخدمها عمليتك فقط وتم تعديلها (أو هي ذاكرة مجهولة). هذا هو الرقم الأكثر أهمية للعثور على التسريبات لأن هذه الذاكرة لا يمكن إسقاطها.
  • ذاكرة الوصول العشوائي (RAM) الخاصة غير المعدَّلة: هي ذاكرة الوصول العشوائي (RAM) التي تستخدمها عمليتك فقط، ولكنها نسخة غير معدَّلة من ملف على مساحة التخزين (مثل ملف DEX). ويمكن أن يتجاهله نظام التشغيل إذا كانت الذاكرة منخفضة.
  • ‫SwapPss: الذاكرة التي تم ضغطها وتبديلها إلى ZRAM. يمكن أن يحدث ذلك إما بسبب ضغط الذاكرة على النظام أو لأنّ وقت تشغيل Android قد حدّد العمليات في الخلفية لضغطها (نقل صفحاتها غير المستخدَمة أو غير النشطة إلى ZRAM). في مثال dumpsys، تم نقل جميع Native Heap وDalvik Heap تقريبًا إلى ZRAM.

  • نقطة أساسية: تمثّل قيمتا أجزاء من ذاكرة Java وأجزاء من الذاكرة الأصلية في قسم "ملخّص التطبيق" الذاكرة المقيمة (المادية). إذا تم ضغط ذاكرة أحد التطبيقات في ZRAM، قد تظهر قيم الملخّص المحدّدة هذه أقل بكثير من المتوقّع. يُرجى دائمًا مراجعة إجمالي مساحة التبديل في PSS أو عمود SwapPss المفصّل للحصول على الصورة الكاملة.

  • ملخّص التطبيق: تصنيف عالي المستوى يجمع الصفوف التفصيلية في مجموعات مفهومة

  • العناصر: مفيدة لتتبُّع نُسخ Activities وViewRootImpl التي تم تسريبها إذا خرجت من تطبيقك وفرضت عملية تجميع المهملات، من المفترض أن يعود عدد Activities إلى 0.

قسم "ميزانية الذاكرة"

في الإصدار 18 من نظام التشغيل Android والإصدارات الأحدث، يطبع dumpsys meminfo أيضًا قسم ميزانية الذاكرة عند تفريغ تطبيق واحد. يقرأ هذا القسم إحصاءات memcg للعملية وللتطبيق بأكمله (الحزمة)، لذا لا تحتاج إلى العثور على ملفات cgroup بنفسك. في ما يلي القسم من نموذج تطبيق MemoryLab في المقدّمة، بعد النقر ثلاث مرات على تخصيص ذاكرة Java (10 ميغابايت):

 Memory Budget
                        Process(KB)       Package(KB)
                        -----------       -----------
  Current Usage:              83820             87976
  Limit:                       none           4718592
  Limit Source:                none     MemoryLimiter
  Peak Usage:                 85136             89292
  Swap Used:                      0                 0

  Usage Breakdown
    Anonymous:                81700             81700
    File:                        96              4200
    File Mapped:                 16               616
    Shared Memory:               16                16
    Kernel:                    1580              1632
    Page Tables:                640               640
    Slab:                       439               458

  Pressure & Reclaim
    Throttle Events:              0                 0
    Refaults (Anon):              0                 0
    Refaults (File):              0                 0
  • الاستخدام الحالي: الذاكرة التي تم تحميلها على memcg (memory.current). هذا هو الرقم الذي تحدّده ميزانيات الذاكرة. في النموذج، ارتفع بمقدار 36,700 كيلوبايت تقريبًا بعد عمليات تخصيص Java الثلاث التي تبلغ سعتها 10 ميغابايت، بما يتماشى مع إجمالي حجم المشاركة النسبي. يكون إجمالي الحزمة أعلى من إجمالي العملية حتى بالنسبة إلى تطبيق أحادي العملية عندما لا تتم استعادة صفحات الملفات المخزّنة مؤقتًا من عملية تشغيل سابقة حتى الآن.
  • الحدّ الأقصى: حدّ الاسترداد (memory.high) الذي ينطبق الآن، أو none في حال عدم توفّر حدّ أقصى. في النموذج، تحتوي الحزمة على حد لذاكرة النظام وليس للعملية حد خاص بها.
  • مصدر الحدّ: مصدر الحدّ. ‫AndroidManifest هو <memory-budget> تم تعريفه في بيان التطبيق، وMemoryBudgetManager هو ميزانية يضبطها التطبيق في وقت التشغيل، وMemoryLimiter هو حدّ أقصى لذاكرة النظام، وam memory-limiter هو تجاوز يدوي تم ضبطه باستخدام adb.
  • ذروة الاستخدام: أعلى رسوم منذ إنشاء memcg (memory.peak). يعرض هذا العمود N/A على النواة التي لا تتوافق معه.
  • ذاكرة الإبدال المستخدمة: الذاكرة المضغوطة في ZRAM (memory.swap.current).
  • تفاصيل الاستخدام: الحقول الرئيسية في memory.stat، مثل الذاكرة المجهولة والذاكرة المستندة إلى الملفات
  • الضغط والاستعادة: يحتسب أحداث التقييد عدد المرات التي تجاوزت فيها مجموعة memcg حدّ memory.high. تحتسب الأخطاء المتكررة الصفحات التي تم تبديلها أو إزالتها ثم أصبحت مطلوبة مرة أخرى. إذا ارتفعت هذه الأرقام أثناء استخدام التطبيق، يكون الحدّ الأقصى أقل من مجموعة العمل الخاصة بالتطبيق.

With the -a or -v flag, the section also lists every raw counter from memory.stat and memory.events.

تمرين تطبيقي: الفرز السريع باستخدام meminfo

يوضّح هذا التمرين كيفية ظهور أنواع مختلفة من عمليات التخصيص في meminfo.

  1. افتح التطبيق:

    adb shell am start -W -n com.android.memorylab/.MainActivity
    
  2. التحقّق من الذاكرة الأساسية:

    adb shell dumpsys meminfo -s com.android.memorylab
    

    (توفّر العلامة -s ملخّصًا موجزًا لفئات الذاكرة).

  3. تخصيص ذاكرة Java: انقر على الزر تخصيص ذاكرة Java (10 ميغابايت) 3 مرات في التطبيق.

  4. ملاحظة التغيير: شغِّل meminfo -s مرة أخرى. من المفترض أن تلاحظ زيادة في قيمة كومة ذاكرة Java بمقدار 30,000 كيلوبايت (30 ميغابايت) تقريبًا.

  5. تخصيص الذاكرة الأصلية: انقر على الزر تخصيص الذاكرة الأصلية (1 غيغابايت غير قابلة للضغط) مرة واحدة.

  6. ملاحظة التغيير: شغِّل meminfo -s مرة أخرى. من المفترض أن تظهر الآن زيادة هائلة في قيمة Native Heap (حوالي 1,000,000 كيلوبايت).

  7. المقارنة مع memcg (الإصدار 18 من Android والإصدارات الأحدث): اطّلِع على قسم Memory Budget في نهاية ناتج meminfo -s. يزداد الاستخدام الحالي مع إجمالي حجم المجموعة المتناسب بعد كل عملية تخصيص، لأنّ عمليات تخصيص Java والعمليات الأصلية يتم تحميلها على memcg الخاص بالتطبيق.

عرض شامل

للاطّلاع على استخدام الذاكرة في النظام بأكمله، نفِّذ الأمر التالي:

adb shell dumpsys meminfo

يقدّم هذا الأمر ملخّصًا لاستخدام ذاكرة الوصول العشوائي (RAM)، بما في ذلك إجمالي مساحة ذاكرة الوصول العشوائي (RAM) ومساحة ذاكرة الوصول العشوائي (RAM) الخالية وقائمة بالعمليات مرتّبة حسب حجم المجموعة التناسبية (PSS).

مقاييس النظام الرئيسية

  • ‫ZRAM: هي مساحة تبديل مضغوطة في ذاكرة الوصول العشوائي (RAM).
  • ‫DMA-BUF: الرسومات ومخازن الذاكرة المشتركة تستخدم إصدارات Android الحديثة DMA-BUF على نطاق واسع لعرض واجهة المستخدم. إذا كان إجمالي DMA-BUF مرتفعًا جدًا، تحقَّق من العمليات التي تتطلّب استخدامًا مكثّفًا للرسومات باستخدام /sys/kernel/debug/dma_buf/bufinfo.
  • ذاكرة الوصول العشوائي المفقودة: الذاكرة التي لا تحتسبها النواة أو أي عملية (مثل المناطق المحجوزة للأجهزة).

إحصاءات memcg

لكل عملية تطبيق مجموعة التحكّم في الذاكرة (memcg) الخاصة بها، وتواصل النواة تشغيل إجماليات الذاكرة التي تم تحميلها عليها (يُرجى الاطّلاع على memcg لمعرفة مدى اختلاف تدوين سجلات الاستخدام هذه عن RSS وPSS). قراءة هذه الملفات سهلة ولا تؤثر في تطبيقك، لذا يمكنك أخذ عيّنات منها كل ثانية أثناء تشغيل مسار المستخدم.

ابحث عن مسار memcg لعملية من السطر 0:: في /proc/<PID>/cgroup بدلاً من إنشاء المسار بنفسك، لأنّ تنسيق cgroup يمكن أن يختلف بين الأجهزة. يجب تمرير كل أمر من أوامر الجهاز إلى adb shell كسلسلة واحدة بين علامات اقتباس لكي يرى غلاف الجهاز علامات الاقتباس:

PID=$(adb shell pidof -s <package_name>)

CG=/sys/fs/cgroup$(adb shell "sed -n 's/^0:://p' /proc/$PID/cgroup")

adb shell "cat $CG/memory.current $CG/memory.swap.current"

adb shell "grep -E '^(anon|file|workingset_refault_.*) ' $CG/memory.stat"

الملفات الرئيسية هي:

  • ‫memory.current: الذاكرة التي تم تحميلها على العملية، بالبايت. هذا هو الرقم الذي تحدده ميزانيات الذاكرة.
  • ‫memory.swap.current: الذاكرة المضغوطة في ZRAM، بالبايت
  • ‫memory.stat: تفاصيل الرسوم، بالبايت للأحجام anon هي ذاكرة مجهولة، مثل Java وأكوام الذاكرة الأصلية. ‫file هو ذاكرة التخزين المؤقت للصفحات المستندة إلى الملفات، مثل الرموز والموارد. يحتسب workingset_refault_anon وworkingset_refault_file الصفحات التي تم تبديلها أو إزالتها ثم الحاجة إليها مرة أخرى. ويعني ارتفاع عدد الأخطاء المتكررة أنّ العملية تفتقر إلى الذاكرة.
  • ‫memory.events: عدد أحداث تجاوز الحدّ الأقصى للذاكرة يحتسب high عدد المرات التي تم فيها تقييد العملية بسبب تجاوز الحد الأقصى المسموح به وهو memory.high.

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

adb shell "cat $CG/../memory.current"

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

للحصول على جولة إرشادية في هذه الملفات باستخدام نموذج تطبيق MemoryLab، يُرجى الاطّلاع على التمرين العملي: استكشاف memcg.

showmap

للحصول على تفاصيل أكثر دقة حول عمليات ربط الذاكرة لعملية معيّنة، استخدِم showmap. تقرأ هذه الأداة من /proc/<pid>/smaps.

adb shell showmap <pid>

يسرد showmap كل خريطة ذاكرة في العملية. تشمل عمليات ربط المفاتيح التي يجب البحث عنها ما يلي:

  • ‫[anon:scudo:primary] أو [anon:libc_malloc]: يشير إلى الذاكرة الديناميكية الأصلية. تستخدم إصدارات Android الحديثة أداة تخصيص Scudo.
  • *.art: صور لأجزاء من الذاكرة في Java (مثل صورة التشغيل)
  • ‫[anon:dalvik-main space]: جزء الذاكرة الرئيسي في Java
  • *.so: المكتبات المشتركة
  • *.{oat,dex,odex,vdex}: رمز DEX المجمَّع

يمكنك ترتيب النتائج للعثور على أكبر مستهلكي الذاكرة الخاصة:

# Sort by private clean + private dirty memory
adb shell showmap <pid> | awk '{print $6+$7, $0}' | sort -rn | head -n 10

مثال على الناتج:

 virtual                     shared   shared  private  private                   Anon      Shmem     File      Shared   Private
    size      RSS      PSS    clean    dirty    clean    dirty     swap  swapPSS HugePages PmdMapped PmdMapped Hugetlb  Hugetlb    Locked    # object
-------- -------- -------- -------- -------- -------- -------- -------- -------- --------- --------- --------- -------- -------- -------- ---- ------------------------------
2776    18176     5784     2823        0     3008      308     2468     4700     4700         0         0         0        0        0        0   47 [anon:scudo:primary]
560      952      876      657      316        0      560        0       12       12         0         0         0        0        0        0    8 /data/app/.../base.apk
448   393216      448      448        0        0        0      448        0        0         0         0         0        0        0        0    2 [anon:dalvik-main space]
...

تمرين تطبيقي: نظرة متعمّقة على showmap

تعرَّف على كيفية تحديد عمليات ربط الذاكرة المحدّدة والتمييز بين الذاكرة النظيفة والذاكرة غير النظيفة.

  1. ذاكرة Mmap Clean: انقر على الزر Mmap Clean Anon Memory (10MB) في تطبيق MemoryLab مرة واحدة.
  2. الحصول على معرّف العملية: تعرض واجهة مستخدم التطبيق معرّف العملية. يمكنك أيضًا العثور عليه باستخدام هذا الأمر:

    adb shell pidof com.android.memorylab
    
  3. الفحص باستخدام showmap:

    # Replace <PID> with the actual process ID from the previous step
    adb shell showmap <PID> | grep "memorylab-clean"
    
  4. تحديد عملية الربط: من المفترض أن تظهر لك عملية ربط مجهولة الهوية تحمل التصنيف [anon:memorylab-clean] بحجم افتراضي يبلغ 10240 كيلوبايت بالضبط. يُرجى العِلم أنّ قيمة الذاكرة الخاصة غير المحرَّرة لهذه الخريطة من المرجّح أن تكون 0. هذه ذاكرة مجهولة "نظيفة" لأنّنا استخدمنا mmap لحجز المساحة الافتراضية ولكن لم نكتب فيها بعد (لذلك لا يتم استهلاك أي ذاكرة وصول عشوائي فعلية).

  5. مقارنة الذاكرة الديناميكية الأصلية: قارِن ذلك بعمليات الربط في [anon:scudo:primary] التي كانت ستظهر في showmap إذا تم تشغيلها بعد الخطوة 5 من تمرين meminfo. بما أنّ عملية تخصيص الذاكرة الأصلية بسعة 1 غيغابايت تؤدي إلى الكتابة في كل صفحة، فإنّ هذه الصفحات تُعدّ "غير صالحة" وستظهر بكثافة في عمود الذاكرة الخاصة غير الصالحة.

  6. نصيحة احترافية (العناصر الكبيرة): يُرجى ملاحظة عمليات تخصيص Java Heap من تمرين meminfo. بما أنّها مصفوفات بايت بحجم 10 ميغابايت، من المرجّح أن تظهر في showmap على شكل [anon:dalvik-free list large object space]. يضع وقت تشغيل Android (ART) العناصر التي يزيد حجمها عن 12 كيلوبايت في مساحة منفصلة للعناصر الكبيرة (LOS) لتجنُّب التجزئة في الذاكرة الديناميكية الرئيسية.

procstats

بينما يوفّر meminfo لقطة في الوقت المناسب، يوفّر procstats بيانات سابقة من خلال أخذ عينات دورية من حالة واستخدام الذاكرة لجميع التطبيقات قيد التشغيل. هذه هي الأداة الأساسية المستخدَمة لمراقبة سلامة الذاكرة على المدى الطويل، وهي التي توجّه إعداد تقارير استخدام الذاكرة في "إعدادات Android".

# View stats for the last 3 hours
adb shell dumpsys procstats --hours 3

فهم الحالات

قد تكون نتائج procstats كثيرة جدًا. الجزء الأكثر أهمية هو المقاييس الموضوعة بين قوسين بجانب كل حالة عملية:

Top: 100% (54MB-64MB-67MB / 49MB-58MB-61MB / 110MB-120MB-130MB over 4)

التنسيق هو: (% time in state) (minPss-avgPss-maxPss / minUss-avgUss-maxUss / minRss-avgRss-maxRss over samples)

  • حجم المجموعة النسبي (Pss): يتم تقسيم الذاكرة المشتركة بشكل نسبي بين العمليات.
  • Uss (حجم المجموعة الفريد): الذاكرة التي تستخدمها هذه العملية فقط (تعادل Private Clean + Private Dirty).
  • Rss (حجم المجموعة المقيمة): إجمالي الذاكرة الفعلية المستخدَمة، بما في ذلك جميع الصفحات المشتركة (يميل إلى الإفراط في العدّ).

أهمية ذلك:

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

حالات العملية

يتتبّع procstats التطبيقات في عدة حالات (غالبًا ما يتم اختصارها):

  • مستمر: عمليات النظام المهمة التي تعمل دائمًا.
  • في المقدّمة: التطبيق قيد الاستخدام حاليًا في المقدّمة ويظهر للمستخدم.
  • ‫ImpFg (عمليات مهمة في المقدّمة): العمليات المهمة في الخلفية (مثل أساليب الإدخال) أو التطبيقات التي تتضمّن خدمة تعمل في المقدّمة
  • ImpBg (عملية مهمة في الخلفية): العمليات في الخلفية التي تُعدّ مهمة للنظام
  • Fgs / BFgs: الخدمات التي تعمل في المقدّمة أو الخدمات المرتبطة التي تعمل في المقدّمة
  • Service / ServRst: الخدمات التي تُشغَّل في الخلفية أو الخدمات التي تتم إعادة تشغيلها حاليًا
  • التطبيقات المخزَّنة مؤقتًا / LastAct: التطبيقات المخزَّنة مؤقتًا في الخلفية لإعادة تشغيلها بسرعة. ويمكن للنظام إيقافها بأمان.

حالة ذاكرة الجهاز وإحصاءات وقت التشغيل

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

Run time Stats:
  SOff/Norm: +1h10m15s
   SOn/Norm: +15m20s
        Low: +2m10s
       Crit: +45s
  • SOff / SOn: يشير هذا الاختصار إلى إيقاف الشاشة مقابل تشغيل الشاشة.
  • Norm / Mod / Low / Crit: مستويات ضغط الذاكرة (عادي، متوسط، منخفض، حرج)

إذا كان الجهاز يقضي وقتًا طويلاً في حالتَي انخفاض أو انخفاض حرج، سيصبح بطيئًا لأنّ النواة تستعيد الذاكرة بشكل كبير وتبدأ ميزة Low Memory Killer (LMK) في إنهاء العمليات في الخلفية. يمكنك بعد ذلك الاطّلاع على الإحصاءات لكل حزمة لمعرفة التطبيقات التي تستهلك قدرًا كبيرًا من استخدام الذاكرة، خاصةً عندما يكون الجهاز في حالات الضغط هذه.


← المفاهيم | ↑ للأعلى | Java →