عندما تحتاج إلى التحقيق في استخدام الذاكرة، ابدأ بأدوات التقييم عالية المستوى لتحديد فئات الذاكرة الأكثر أهمية.
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.
افتح التطبيق:
adb shell am start -W -n com.android.memorylab/.MainActivityالتحقّق من الذاكرة الأساسية:
adb shell dumpsys meminfo -s com.android.memorylab(توفّر العلامة
-sملخّصًا موجزًا لفئات الذاكرة).تخصيص ذاكرة Java: انقر على الزر تخصيص ذاكرة Java (10 ميغابايت) 3 مرات في التطبيق.
ملاحظة التغيير: شغِّل
meminfo -sمرة أخرى. من المفترض أن تلاحظ زيادة في قيمة كومة ذاكرة Java بمقدار 30,000 كيلوبايت (30 ميغابايت) تقريبًا.تخصيص الذاكرة الأصلية: انقر على الزر تخصيص الذاكرة الأصلية (1 غيغابايت غير قابلة للضغط) مرة واحدة.
ملاحظة التغيير: شغِّل
meminfo -sمرة أخرى. من المفترض أن تظهر الآن زيادة هائلة في قيمة Native Heap (حوالي 1,000,000 كيلوبايت).المقارنة مع 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
تعرَّف على كيفية تحديد عمليات ربط الذاكرة المحدّدة والتمييز بين الذاكرة النظيفة والذاكرة غير النظيفة.
- ذاكرة Mmap Clean: انقر على الزر Mmap Clean Anon Memory (10MB) في تطبيق MemoryLab مرة واحدة.
الحصول على معرّف العملية: تعرض واجهة مستخدم التطبيق معرّف العملية. يمكنك أيضًا العثور عليه باستخدام هذا الأمر:
adb shell pidof com.android.memorylabالفحص باستخدام showmap:
# Replace <PID> with the actual process ID from the previous step adb shell showmap <PID> | grep "memorylab-clean"تحديد عملية الربط: من المفترض أن تظهر لك عملية ربط مجهولة الهوية تحمل التصنيف
[anon:memorylab-clean]بحجم افتراضي يبلغ 10240 كيلوبايت بالضبط. يُرجى العِلم أنّ قيمة الذاكرة الخاصة غير المحرَّرة لهذه الخريطة من المرجّح أن تكون 0. هذه ذاكرة مجهولة "نظيفة" لأنّنا استخدمناmmapلحجز المساحة الافتراضية ولكن لم نكتب فيها بعد (لذلك لا يتم استهلاك أي ذاكرة وصول عشوائي فعلية).مقارنة الذاكرة الديناميكية الأصلية: قارِن ذلك بعمليات الربط في
[anon:scudo:primary]التي كانت ستظهر فيshowmapإذا تم تشغيلها بعد الخطوة 5 من تمرينmeminfo. بما أنّ عملية تخصيص الذاكرة الأصلية بسعة 1 غيغابايت تؤدي إلى الكتابة في كل صفحة، فإنّ هذه الصفحات تُعدّ "غير صالحة" وستظهر بكثافة في عمود الذاكرة الخاصة غير الصالحة.نصيحة احترافية (العناصر الكبيرة): يُرجى ملاحظة عمليات تخصيص 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 →