وقتی لازم است مصرف حافظه را بررسی کنید، با ابزارهای ارزیابی سطح بالا شروع کنید تا مشخص کنید کدام دستههای حافظه مهمتر هستند.
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
...
تفسیر دادهها
- Pss Total: اندازه مجموعه متناسب. این مجموع
Private DirtyاستPrivate Clean+ سهم عادلانه فرایند شما از حافظه همرسانیشده با فرایندهای دیگر (مثل تصویر راهاندازی Zygote). این بهترین سنجه برای «این برنامه مسئول چه مقدار حافظه است» است.
- Private Dirty: این حافظه دسترسی تصادفی است که فقط فرایند شما از آن استفاده میکند و اصلاح شده است (یا حافظه ناشناس است). این مهمترین عدد برای پیدا کردن نشت است، زیرا این حافظه را نمیتوان رها کرد.
- پاکسازی خصوصی: RAM که فقط فرایند شما از آن استفاده میکند، اما نسخه اصلاحنشدهای از فایل در فضای ذخیرهسازی است (مانند فایل DEX). اگر حافظه کم باشد، سیستمعامل میتواند آن را رها کند.
- SwapPss: حافظهای که فشرده شده و به ZRAM مبادله شده است. این اتفاق ممکن است بهدلیل فشار حافظه بر سیستم یا بهدلیل شناسایی فرایندهای پسزمینهای توسط «زمان اجرای Android» برای فشردهسازی (انتقال صفحههای غیرفعال یا استفادهنشده به ZRAM) رخ دهد. در مثال
dumpsys، تقریباً همهNative HeapوDalvik Heapبه ZRAM منتقل شدهاند.
- خلاصه برنامه: دستهبندی سطح بالاتری که ردیفهای دقیق را در دستههای قابلفهم ترکیب میکند.
- اشیاء: برای ردیابی نمونههای
ActivitiesوViewRootImplنشتکرده مفید است. اگر از برنامهتان خارج شوید و GC را اجباری کنید،Activitiesتعداد باید به ۰ برگردد.
بخش بودجه حافظه
در Android 18 و نسخههای بالاتر، dumpsys meminfo همچنین هنگام تخلیه یک برنامه، بخش بودجه حافظه را چاپ میکند. این بخش آمار memcg فرایند و کل برنامه (بسته) را میخواند، بنابراین نیازی نیست خودتان فایلهای cgroup را پیدا کنید. در اینجا بخشی از برنامه نمونه MemoryLab در پیشزمینه، پساز سه بار تکضرب زدن روی اختصاص دادن حافظه Java (۱۰ مگابایت) نشان داده شده است:
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). این عددی است که بودجه حافظه محدود میکند. در نمونه، پساز سه تخصیص ۱۰ مگابایتی Java، حدود ۳۶٬۷۰۰ کیلوبایت افزایش یافت که با TOTAL PSS مطابقت دارد. مجموع بسته بیشتر از مجموع فرایند است، حتی برای برنامه تکفرایندی که صفحههای فایل ذخیرهشده در حافظه نهان از اجرای قبلی هنوز بازیابی نشدهاند. - حد: آستانه بازپسگیری (
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خود فراتر رفته است میشمارد. خطاهای مجدد صفحاتی را میشمارد که جابهجا یا تخلیه شدهاند و سپس دوباره موردنیاز بودهاند. اگر این اعداد هنگام استفاده از برنامه افزایش یابند، حد مجاز از مجموعه کاری برنامه تنگتر است.
با پرچم -a یا -v، این بخش همچنین همه شمارندههای خام از
memory.stat و memory.events را فهرست میکند.
تمرین عملی: تریاژ سریع با meminfo
این تمرین نشان میدهد که انواع مختلف تخصیصها چگونه در meminfo نشان داده میشوند.
راهاندازی برنامه:
adb shell am start -W -n com.android.memorylab/.MainActivityبررسی حافظه پایه:
adb shell dumpsys meminfo -s com.android.memorylab(پرچم
-sخلاصه کوتاه و مختصری از دستههای حافظه ارائه میدهد).اختصاص دادن حافظه جاوا: روی دکمه اختصاص دادن حافظه جاوا (۱۰ مگابایت) ۳ بار در برنامه تکضرب بزنید.
مشاهده تغییر:
meminfo -sرا دوباره اجرا کنید. باید ببینید که مقدار Java Heap تقریباً ۳۰٬۰۰۰ کیلوبایت (۳۰ مگابایت) افزایش مییابد.اختصاص دادن حافظه بومی: روی دکمه اختصاص دادن حافظه بومی (۱ گیگابایت غیرقابل فشردهسازی) یکبار تکضرب بزنید.
مشاهده تغییر:
meminfo -sرا دوباره اجرا کنید. اکنون مقدار Native Heap باید افزایش قابلتوجهی را نشان دهد (حدود ۱٬۰۰۰٬۰۰۰ کیلوبایت).مقایسه با memcg (Android 18 و بالاتر): به بخش بودجه حافظه در انتهای خروجی
meminfo -sنگاه کنید. Current Usage grows along with TOTAL PSS after each allocation, because both the Java and the native allocations are charged to the app's 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) خودش را دارد و هسته مجموع حافظه اختصاصدادهشده به آن را بهطور مداوم نگه میدارد (برای اطلاع از تفاوت این حسابداری با RSS و PSS، memcg را ببینید). خواندن این فایلها ارزان است و برنامه شما را مختل نمیکند، بنابراین میتوانید هر ثانیه که جریان کاربر را اجرا میکنید، از آنها نمونهبرداری کنید.
مسیر 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حافظه ناشناس است، مثل تودههای جاوا و بومی.fileحافظه نهان صفحه با پشتیبانی فایل است، مانند کد و منابع.workingset_refault_anonوworkingset_refault_fileصفحاتی را که جابهجا یا تخلیه شدهاند و سپس دوباره موردنیاز بودهاند میشمارد. افزایش تعداد خطاهای ارجاع مجدد به این معنی است که فرایند با کمبود حافظه مواجه است.memory.events: تعداد رویدادهای محدودیت حافظه.highتعداد دفعاتی را که فرایند بهدلیل فراتر رفتن از حد مجازmemory.highمحدود شده است میشمارد.
دایرکتوری والد memcg فرایند به کل برنامه (UID آن) تعلق دارد. فایلهای آن شامل حافظه همه فرایندهای درحال اجرای برنامه، بهعلاوه صفحههای فایل ذخیرهشده در حافظه نهان است که از فرایندهای قبلی برنامه که خارج شدهاند و هنوز بازیابی نشدهاند باقی مانده است:
adb shell "cat $CG/../memory.current"
اگر خطای اجازه دریافت کردید، adb root را اجرا کنید و دوباره امتحان کنید. برخیاز دستگاهها فقط
به شما اجازه میدهند این فایلها را بهعنوان ریشه بخوانید.
برای راهنمای گامبهگام این فایلها با برنامه نمونه MemoryLab، به تمرین عملی: کاوش memcg مراجعه کنید.
نمایش نقشه
برای تفکیک بسیار دقیقتر نگاشتهای حافظه فرایند، از
showmap استفاده کنید. این ابزار از /proc/<pid>/smaps میخواند.
adb shell showmap <pid>
showmap همه نگاشتهای حافظه را در فرایند فهرست میکند. نگاشتهای کلید که باید بهدنبالشان باشید
عبارتاند از:
[anon:scudo:primary]یا[anon:libc_malloc]: پشته بومی. نسخههای مدرن Android از تخصیصدهنده Scudo استفاده میکنند.*.art: تصاویر پشته جاوا (مثلاً تصویر راهاندازی).[anon:dalvik-main space]: پشته اصلی جاوا.*.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 Memory (پاک کردن حافظه Mmap): روی دکمه Mmap Clean Anon Memory (10MB) (پاک کردن حافظه ناشناس Mmap (۱۰ مگابایت)) در برنامه 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]و اندازه مجازی دقیقاً ۱۰۲۴۰ کیلوبایت مشخص شده است. توجه داشته باشید که مقدار خصوصی کثیف برای این نقشه احتمالاً ۰ است. این حافظه ناشناس «تمیز» است زیرا ازmmapبرای رزرو فضای مجازی استفاده کردیم اما هنوز چیزی در آن ننوشتهایم (بنابراین هیچ RAM فیزیکیای واقعاً مصرف نشده است).مقایسه پشته بومی: این را با نگاشتهای
[anon:scudo:primary]مقایسه کنید که اگر آن را بعداز مرحله ۵ تمرینmeminfoاجرا کرده بودید، درshowmapظاهر میشد. چون آن ۱ گیگابایت تخصیص بومی درواقع در هر صفحه مینویسد، آن صفحهها «کثیف» هستند و بهشدت در ستون کثیف خصوصی نشان داده میشوند.نکته حرفهای (اشیاء بزرگ): به تخصیصهای Java Heap از تمرین
meminfoتوجه کنید. چون آرایههای بایت ۱۰ مگابایتی هستند، احتمالاً درshowmapبهصورت[anon:dalvik-free list large object space]نشان داده میشوند. زمان اجرای Android (ART) اشیای بزرگتر از ۱۲ کیلوبایت را در فضای شیء بزرگ (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)
قالب: (% زمان در وضعیت) (minPss-avgPss-maxPss / minUss-avgUss-maxUss / minRss-avgRss-maxRss در نمونهها)
- Pss (اندازه مجموعه نسبی): حافظه مشترک بهطور نسبی بین فرایندها تقسیم میشود.
- Uss (اندازه مجموعه منحصربهفرد): حافظهای که فقط توسط این فرایند استفاده میشود (معادل Private Clean + Private Dirty).
- Rss (Resident Set Size): کل حافظه فیزیکی استفادهشده، شامل همه صفحههای همرسانیشده (معمولاً بیشاز حد واقعی محاسبه میشود).
چرا این موضوع اهمیت دارد:
- برنامه پایدار: حداقل/ میانگین/ حداکثر به هم نزدیک هستند.
- نشت حافظه: حداکثر بهطور قابلتوجهی بالاتر از حداقل است و با افزایش تعداد نمونه، میانگین بهمرور زمان افزایش مییابد.
- استفاده با جهشهای ناگهانی: حداکثر بسیار بالاتر از میانگین است که نشاندهنده عملیات سنگین گذرا (مانند پردازش تصویر) است که اگر در زمان نامناسبی رخ دهند، ممکن است باعث کمبود حافظه شوند.
وضعیتهای فرایند
procstats برنامهها را در چندین ایالت (اغلب بهصورت اختصاری) ردیابی میکند:
- Persist: فرایندهای حیاتی سیستم که همیشه درحال اجرا هستند.
- بالا: برنامه درحالحاضر در پیشزمینه است و برای کاربر قابلمشاهده است.
- ImpFg (پیشزمینه مهم): فرایندهای پسزمینه حیاتی (مانند IME) یا برنامههای دارای سرویس پیشزمینه.
- ImpBg (پسزمینه مهم): فرایندهای پسزمینهای که برای سیستم مهم تلقی میشوند.
- Fgs / BFgs: سرویسهای پیشزمینهای یا سرویسهای پیشزمینهای محدودشده.
- سرویس / بازراهاندازی سرویس: سرویسهای پسزمینه یا سرویسهایی که درحالحاضر بازراهاندازی میشوند.
- ذخیرهشده در حافظه نهان / LastAct: برنامههایی که برای بازراهاندازی سریع در پسزمینه در حافظه نهان ذخیره شدهاند. سیستم میتواند این برنامهها را بدون مشکل متوقف کند.
وضعیت حافظه دستگاه و آمار زمان اجرا
procstats همچنین مدتزمانی را که کل دستگاه تحت فشار حافظه بوده است ردیابی میکند. بهدنبال بخش آمار زمان اجرا بگردید:
Run time Stats:
SOff/Norm: +1h10m15s
SOn/Norm: +15m20s
Low: +2m10s
Crit: +45s
- SOff / SOn: صفحه خاموش درمقابل صفحه روشن.
- Norm / Mod / Low / Crit: سطوح فشار حافظه (عادی، متوسط، کم، بحرانی).
اگر دستگاه زمان قابلتوجهی را در وضعیت کم یا بحرانی سپری کند، کند بهنظر میرسد زیرا هسته بهطور تهاجمی حافظه را بازیابی میکند و «بستن ناشی از کمبود حافظه» (LMK) شروع به خاتمه دادن به فرایندهای پسزمینه میکند. سپس میتوانید آمار هر بسته را بررسی کنید تا ببینید کدام برنامهها بهطور خاص وقتی دستگاه در این وضعیتهای تحت فشار قرار دارد، مصرف حافظه بالایی دارند.