درحال تجزیه‌وتحلیل حافظه جاوا

برنامه‌های Java و Kotlin حافظه را ازطریق پشته جمع‌آوری زباله مدیریت می‌کنند. وقتی اشیاء دیگر قابل‌دسترسی نباشند، جمع‌آوری‌کننده زباله (GC) درنهایت فضای آن‌ها را بازیابی می‌کند. نشت حافظه زمانی رخ می‌دهد که اشیایی که دیگر موردنیاز نیستند همچنان توسط «ریشه‌های GC» نگهداری می‌شوند و از بازیابی آن‌ها جلوگیری می‌شود.

مفاهیم اصلی

ریشه‌های جمع‌آوری زباله

GC Root نوع خاصی از شیء است که زباله‌روب آن را به‌عنوان همیشه دردسترس درنظر می‌گیرد. چند نمونه:

  • رشته‌های فعال (و اشیایی که از چارچوب‌های پشته‌ای Java درحال اجرای فعلی‌شان به آن‌ها ارجاع داده شده است).
  • کلاس‌هایی که روش‌های فعال دارند.
  • مرجع‌های JNI (مرجع‌های جهانی یا محلی که توسط کد بومی نگهداری می‌شوند).

مسیر به ریشه جمع‌آوری زباله

تا زمانی که زنجیره‌ای از ارجاع‌ها از GC Root به یک شیء وجود داشته باشد، آن شیء «قابل‌دسترسی» است و نمی‌توان آن را جمع‌آوری زباله کرد. این زنجیره مسیر به ریشه GC نامیده می‌شود. برای رفع نشت حافظه، باید این زنجیره را شناسایی و قطع کنید.

مسیر به ریشه جمع‌آوری زباله

درخت‌های مسلط

درحالی‌که مسیر به ریشه GC به شما می‌گوید چرا یک شیء زنده است، به شما نمی‌گوید که اگر آن مرجع شکسته شود، چقدر حافظه بازیابی می‌شود. برای این کار، از درخت‌های مسلط استفاده می‌کنیم.

گفته می‌شود شیء A بر شیء B تسلط دارد اگر هر مسیر از هر ریشه GC به B باید از A عبور کند. اگر A بر B غالب باشد، پس بازپس‌گیری A تضمین می‌کند که B نیز می‌تواند بازپس گرفته شود، زیرا هیچ مسیر دیگری از هیچ ریشه‌ای به B وجود ندارد.

نمودار زیر گراف شیء و درخت غالب مربوط به آن را نشان می‌دهد. توجه کنید که چگونه شیء D هم توسط A و هم B در نمودار قابل‌دسترسی است، بنابراین نه A و نه B بر D تسلط ندارند؛ درعوض، ریشه GC نزدیک‌ترین مسلط بر آن است.

درخت مسلط

درحال دریافت رونوشت‌های پشته جاوا

رونوشت پشته عکس فوری از همه اشیاء در پشته 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

به رونوشت‌های پشته جاوا در اسناد Perfetto مراجعه کنید.

درحال تجزیه‌وتحلیل با AHAT

AHAT (ابزار تجزیه‌وتحلیل پشته Android) ابزار توصیه‌شده برای مشاهده فایل‌های .hprof در مرورگر وب است.

درحال شروع «زمان رسیدگی به کمک‌های هوش مصنوعی»

اگر ahat را در مسیرتان نصب کرده‌اید، آن را با این دستور راه‌اندازی کنید:

ahat heap.hprof

یا jar خوداتکا را اجرا کنید:

java -jar ahat.jar heap.hprof

سپس مرورگرتان را در http://localhost:7100 باز کنید.

برای جزئیات مربوط به دریافت یا ساخت AHAT، به مخزن منبع AHAT مراجعه کنید.

گردش کارهای تحلیل کلیدی

درحال پیدا کردن نشتی‌ها

کلاس «فعالیت» (MainActivity) خود را در نمای تخصیص‌ها جستجو کنید.

نمای AHAT که نمونه‌ها را نشان می‌دهد

روی کلاس کلیک کنید تا همه نمونه‌ها را پیدا کنید.

نمای AHAT که نمونه‌های MainActivity را نشان می‌دهد روی نمونه MainActivity کلیک کنید تا آن را بازرسی کنید.

AHAT که جزئیات نمونه را نشان می‌دهد

در نمای نمونه، می‌توانید مسیر نمونه از GC Root را پیدا کنید که زنجیره ارجاعاتی را نشان می‌دهد که مانع از جمع‌آوری زباله شیء می‌شود، و اندازه شیء که نشان می‌دهد چه مقدار حافظه توسط این نمونه خاص حفظ می‌شود.

مسیر نمونه AHAT از ریشه GC و اندازه شیء

درحال تجزیه‌وتحلیل نقشه‌های بیتی

AHAT پشتیبانی ویژه‌ای برای مشاهده اشیاء android.graphics.Bitmap دارد که اغلب مصرف‌کنندگان بزرگ حافظه هستند. روی نمونه Bitmap کلیک کنید تا پیش‌نمایش پردازش‌شده محتوای آن را ببینید.

پیش‌نمایش بیت‌مپ AHAT

صفحه نشت فعالیت

AHAT نمای تخصصی‌ای برای شناسایی «فعالیت‌های» نشت‌کرده دارد که یکی از رایج‌ترین و تأثیرگذارترین نشت‌های حافظه در Android است.

  1. کنش: در MemoryLab، روی Leak an Activity (لو دادن فعالیت) تک‌ضرب بزنید. این کار LeakedActivity را راه‌اندازی می‌کند که عمداً خودش را نشت می‌دهد.
  2. Dump: رونوشت پشته بگیرید.
  3. تجزیه‌وتحلیل: روی نشت فعالیت در نوار کناری AHAT کلیک کنید.
  4. درستی‌سنجی: AHAT‏ com.android.memorylab.LeakedActivity را به‌عنوان نشت‌کرده فهرست می‌کند زیرا فیلد mDestroyed آن درست است (نشان‌دهنده پایان چرخه حیات «فعالیت») اما همچنان از ریشه GC قابل‌دسترسی است.

AHAT Activity Leaks Page

درحال مقایسه رونوشت‌های پشته

مقایسه دو رونوشت پشته یکی از قدرتمندترین روش‌ها برای شناسایی مشکلات حافظه است. با مقایسه یک تخلیه پایه «تمیز» با تخلیه‌ای که پس‌از انجام برخی اقدامات گرفته شده است، می‌توانید فوراً ببینید کدام اشیاء انباشته شده‌اند.

تمرین: شناسایی نشت‌ها ازطریق Diffing

  1. مقدار پایه: MemoryLab را راه‌اندازی کنید و رونوشت پشته مقدار پایه را بگیرید:

    adb shell am dumpheap com.android.memorylab /data/local/tmp/base.hprof
    adb pull /data/local/tmp/base.hprof .
    
  2. کنش: چند بار روی اختصاص حافظه Java‏ (۱۰ مگابایت) در برنامه ضربه بزنید.

  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 با Delta

  1. Drill Down (بررسی جزئیات): روی rooted (ریشه‌دار) در منو کلیک کنید. این صفحه اشیای قابل‌دسترسی از ریشه‌های GC را نشان می‌دهد که براساس اندازه حفظ‌شده‌شان مرتب شده‌اند. MainActivity را در بالا با دلتای مثبت بزرگ خواهید دید.

نمای ریشه‌دار AHAT با دلتا

درحال ضبط ردیابی پشته‌ای تخصیص

اگرچه مسیر نمونه از 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‏ (۱۰ مگابایت) تک‌ضرب بزنید.

  3. Dump: رونوشت پشته بگیرید و آن را بکشید.

  4. تحلیل: رونوشت را در AHAT باز کنید. به نمونه بزرگ byte[] پیمایش کنید. (مثلاً، بازرسی MainActivity ← mJavaAllocations (ArrayList) ← elementData (Object[]) ← عنصر آرایه [0]).

  5. درستی‌سنجی: در نمای نمونه، به بخش سایت تخصیص نگاه کنید. رد پشته کامل منتهی به MainActivity.allocateJava را نشان می‌دهد.

سایت تخصیص AHAT

شکار رشته‌های تکراری و تورم آب‌رسانی

حتی وقتی برنامه‌ای نشت GC-root کلاسیک ندارد، انبوهه Java زنده آن می‌تواند با هزاران نمونه تکراری java.lang.String که درطول غیرسریالی‌سازی JSON،‏ Protobuf،‏ Cursor، یا پایگاه داده Room ایجاد شده‌اند، متورم شود. کلیدهای تکراری، رشته‌های وضعیت، برچسب‌های دسته، یا نشانی‌های وب اغلب در هر پاسخ شبکه یا پرسمان پایگاه داده دوباره اختصاص داده می‌شوند. در برنامه‌های بزرگ فید، پیام‌رسانی، و محتوا، رشته‌های تکراری معمولاً ۳۰٪ تا ۶۰٪ از حافظه String زنده را تشکیل می‌دهند.

برای بررسی رشته‌های تکراری در AHAT:

  1. صفحه تخصیص‌ها را باز کنید و براساس java.lang.String فیلتر کنید.
  2. هنگام مقایسه دو رونوشت پشته با --baseline، بررسی کنید که آیا java.lang.String تعداد نمونه‌ها و کل بایت‌ها به‌طور نامتناسبی پس‌از هیدراته کردن فید یا بار کردن حافظه نهان محلی افزایش می‌یابد یا نه.
  3. جدول نمونه‌های java.lang.String را (مرتب‌شده براساس اندازه یا مقدار) مرور کنید تا مقادیر رشته‌ای یکسانی را که در چندین شیء مدل درون‌حافظه‌ای حفظ شده‌اند شناسایی کنید.
  • راه‌حل: از فراخوانی بی‌رویه String.intern() در ورودی‌های دلخواه کاربر یا شبکه خودداری کنید، زیرا جدول داخلی زمان اجرا سراسری است و می‌تواند باعث رقابت قفل شود یا رشته‌ها را طولانی‌تر از حد نیاز نگه دارد. درعوض، رشته‌های دامنه با بسامد بالا را درطول واگرداندن بااستفاده از حافظه پنهان حذف تکرار محدود و محدودشده (مانند LruCache<String, String> درون تجزیه‌کننده یا آداپتور) تکرارزدایی کنید، یا مجموعه‌های ثابت مقادیر را به‌عنوان شمارش‌ها یا ثابت‌های عدد صحیح نشان دهید.

درحال تحلیل پویایی حافظه جاوا (نمایه ترکیبی)

برای دریافت تصویری کامل از رفتار حافظه برنامه، می‌توانید شمارنده‌های حافظه، فعالیت رشته، و نمایه‌سازی تخصیص مبتنی بر پشته فراخوانی را در یک ردیابی Perfetto ترکیب کنید. این کار به شما امکان می‌دهد سنجه‌های حافظه سراسر سیستم (مانند RSS و اندازه توده) را با سایت‌های اختصاص و اجرای کد خاص مرتبط کنید.

از پیکربندی ترکیبی استفاده خواهیم کرد که موارد زیر را فعال می‌کند:

  • شمارنده‌های حافظه (linux.process_stats): RSS و سایر سنجه‌های حافظه را نظرسنجی می‌کند.
  • ‫ATrace (دسته‌های dalvik،‏ memory،‏ sched): وضعیت‌های رشته و رویدادهای GC را ضبط می‌کند.
  • Heapprofd‏ (android.heapprofd): هر دو پشته com.android.art (Java) و libc.malloc (بومی) را با رونوشت‌های مداوم هر ۵ ثانیه هدف قرار می‌دهد.

تمرین: تجزیه‌وتحلیل حافظه ترکیبی

در این تمرین، برنامه MemoryLab را اجرا می‌کنیم و دنباله‌ای از عملیات حافظه را انجام می‌دهیم تا الگوهای مختلف را در ردگیری مشاهده کنیم:

  1. مبنا: حالت آزاد.
  2. Java Churn: تخصیص‌های موقتی که بلافاصله جمع‌آوری زباله می‌شوند.
  3. تخصیص جاوا پایدار: تخصیص اشیاء جاوا که در حافظه باقی می‌مانند.
  4. تخصیص بیت‌مپ: تخصیص دارایی‌های گرافیکی بزرگ (که در حافظه گرافیکی/ توده بومی قرار دارند).
  5. احیا: آزاد کردن همه منابع اختصاص‌داده‌شده.

۱. راه‌اندازی و آماده‌سازی

  1. برنامه را اجباراً متوقف و بازراه‌اندازی کنید تا مطمئن شوید که در وضعیت تمیز قرار دارد:

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

۲. شروع ردیابی و راه‌اندازی توالی

ردگیری ۴۰ ثانیه‌ای را شروع می‌کنیم و رویدادهای حافظه را بااستفاده از فرمان‌های 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 شروع کنید و پشته‌های جاوا و بومی را با رونوشت‌های پیوسته هدف قرار دهید:

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

۳. درحال تحلیل ردگیری ترکیبی

java_memory.perfetto-trace جمع‌آوری‌شده را در رابط کاربری Perfetto باز کنید.

قطعه‌های کلیدی در Perfetto

قبل‌از تجزیه‌وتحلیل خط زمان، این قطعه‌های ضروری را برای فرایند com.android.memorylab پیدا کنید:

  1. mem.rss.anon (RSS ناشناس): در بخش حافظه پردازش یافت می‌شود. این قطعه حافظه فیزیکی (RAM) اختصاص‌داده‌شده به فرایند توسط سیستم‌عامل را اندازه‌گیری می‌کند. این مقدار نشان‌دهنده ردپای حافظه واقعی است.
  2. Heap size (KB): همچنین در بخش حافظه. این شمارنده مختص Dalvik/ART است که فضای نشانی مجازی رزروشده برای پشته Java را نشان می‌دهد. این مقدار نشان‌دهنده حد پشته داخلی ماشین مجازی است که با اختصاص یافتن اشیا و اجرای GC نوسان می‌کند.
  3. HeapTaskDaemon: در فهرست رشته‌های زیر فرایند پیدا شد. این رشته پس‌زمینه‌ای است که در آن ART Garbage Collector بیشتر کار خود را انجام می‌دهد. فعالیت در اینجا نشان‌دهنده مجوزهای فعال GC است.
  4. Continuous Allocation Dumps (heapprofd): به‌صورت برش‌های رنگی درطول خط زمان بالا نشان داده می‌شود. هر برش نشان‌دهنده یک مدت زمان است. با کلیک کردن روی یک برش یا انتخاب کردن یک بازه زمانی می‌توانید Flamegraph (در قاب پایین) را برای com.android.art (اختصاص‌های Java) یا libc.malloc (اختصاص‌های Native) بررسی کنید تا ببینید در آن دوره چه چیزی اختصاص داده شده است.

تحلیل فاز زمانی

بیایید ردگیری را به‌ترتیب زمانی مرور کنیم تا ببینیم این قطعه‌ها چگونه درطول هر فاز تمرین با یکدیگر تعامل دارند.

مرحله ۱: خط مبنا (۰ تا ۵ ثانیه)
  • چه اتفاقی می‌افتد: برنامه غیرفعال است و منتظر فرمان‌ها است.
  • وضعیت قطعه:
    • mem.rss.anon: خط صاف در خط مبنا (معمولاً حدود ۶۰ تا ۸۰ مگابایت بسته به دستگاه).
    • ‫Heap size (KB): خط صاف، مطابق با تخصیص اولیه پشته جاوا.
    • ‫HeapTaskDaemon: غیرفعال (هیچ برشی اجرا را نشان نمی‌دهد).
    • Allocation Dumps: تخصیص‌های خط پایه حداقلی را نشان می‌دهد.

میانای کاربر Perfetto که خط پایه فاز ۱ را نشان می‌دهد

فاز ۲: ریزش تخصیص جاوا (۵ ثانیه - ۱۵ ثانیه)
  • چه اتفاقی می‌افتد: AllocationChurnThread شروع می‌شود، به‌طور مکرر آرایه‌های ۱ مگابایتی را اختصاص می‌دهد و آن‌ها را دور می‌اندازد.
  • وضعیت قطعه:
    • Heap size (KB): الگوی دندانه‌اره‌ای سریعی را نشان می‌دهد. اندازه توده با انباشته شدن تخصیص‌ها افزایش می‌یابد و با اجرای GC به‌شدت کاهش می‌یابد.
    • HeapTaskDaemon: فعالیت تقریباً ثابتی را نشان می‌دهد، با برش‌های اجرایی که کاملاً با افت‌های دندان اره‌ای Heap size هم‌راستا هستند.
    • ‫mem.rss.anon: فعالیت پشته جاوا را ردیابی می‌کند.
    • رونوشت‌های تخصیص پشته جاوا: با انتخاب برش‌ها در این قطعه، تخصیص‌های پشته com.android.art نشان داده می‌شود.

میانای کاربر Perfetto که ریزش فاز ۲ را نشان می‌دهد نمونه‌های تخصیص نشان می‌دهد که AllocationChurnThread تخصیص‌دهنده اصلی است، و همه تخصیص‌ها پشته فراخوانی یکسانی را هم‌رسانی می‌کنند که به لامبدا در داخل MainActivity.java اشاره می‌کند.

میانای کاربری Perfetto که تخصیص‌های جاوا در فاز ۲ را نشان می‌دهد

فاز ۳: تخصیص جاوا پایدار (۱۵ ثانیه - ۲۰ ثانیه)
  • چه اتفاقی می‌افتد: ۱۰ مگابایت از اشیای Java را اختصاص می‌دهیم و مرجعی به آن‌ها در mJavaAllocations نگه می‌داریم.
  • وضعیت قطعه:
    • Heap size (KB): خط مبنای دندانه‌اره‌ای تقریباً ۱۰ مگابایت افزایش می‌یابد.
    • mem.rss.anon: Steps up by approximately 10MB, as the OS must back this persistent allocation with new physical pages.
    • رونوشت‌های تخصیص پشته جاوا: با انتخاب برش‌ها در این قطعه، تخصیص‌های پشته com.android.art نشان داده می‌شود.
    • تخلیه‌های تخصیص (نمودار شعله‌ای): بررسی توده com.android.art برای تخلیه انجام‌شده در این پنجره، مسیر تخصیص جدیدی را از MainActivity.allocateJava نشان می‌دهد که به اندازه حفظ‌شده کمک می‌کند.

میانای کاربری Perfetto که تخصیص Java پایدار فاز ۳ را نشان می‌دهد نمونه تخصیصی را انتخاب کنید که مدت آن با افزایش ۱۰ مگابایتی تخصیص پایدار هم‌پوشانی داشته باشد. باید ببینید که پشته‌های فراخوانی تخصیص به دو سایت مختلف واگرا می‌شوند، یکی مسئول همان ریزش تخصیص کوتاه‌مدت است که قبلاً دیدیم، و دیگری برای تخصیص بلندمدت جدید است.

میانای کاربری Perfetto که تخصیص‌های جاوا در فاز ۳ را نشان می‌دهد

فاز ۴: تخصیص بیت‌مپ (۲۰ تا ۳۰ ثانیه)
  • چه اتفاقی می‌افتد: همچنین ۲۰ مگابایت به Bitmaps اختصاص می‌دهیم.
  • وضعیت قطعه:
    • ‫Heap size (KB): مثل قبل.
    • mem.rss.anon: افزایش قابل‌توجهی حدود ۲۰ مگابایت را نشان می‌دهد، که مربوط به تخصیص‌های بومی برای داده‌های پیکسل Bitmap است.
    • ‫Allocation Dumps (Flamegraph): این بار روی برش‌های مربوط به توده libc.malloc (Native) تمرکز کنید.

میانای کاربری Perfetto که «تخصیص بیت‌مپ فاز ۴» را نشان می‌دهد پشته‌های فراخوانی تخصیص بومی نشان می‌دهند که تخصیص Bitmap از کتابخانه‌های گرافیکی بومی منشأ می‌گیرد. این مورد استفاده خوبی برای ردیابی تخصیص بومی است، زیرا این تخصیص‌های Bitmap را در پشته Java نخواهید دید.

میانای کاربری Perfetto که تخصیص‌های بومی فاز ۴ را نشان می‌دهد

مرحله ۵: احیا (۳۰ تا ۴۰ سالگی)
  • چه اتفاقی می‌افتد: ما FREE_ALL را راه‌اندازی می‌کنیم، ارجاعات به همه تخصیص‌های Java و نقشه بیتی پایدار را پاک می‌کنیم، و سپس یک System.gc() صریح را اجرا می‌کنیم.
  • وضعیت قطعه:
    • Heap size (KB): به سطح پایه برمی‌گردد.
    • mem.rss.anon: دوباره پایین می‌آید و نشان می‌دهد که سیستم‌عامل صفحات فیزیکی را پس می‌گیرد.
    • HeapTaskDaemon: آخرین فوران فعالیت را هنگام پردازش جمع‌آوری زباله نشان می‌دهد.

میانای کاربری Perfetto که «مرحله ۵: بازیابی» را نشان می‌دهد

پایش کردن OOMهای گذشته (ApplicationExitInfo)

گرفتن «بستن ناشی از کمبود حافظه» درحین وقوع برای اشکال‌زدایی فعال عالی است، اما برای دورسنجی میدانی، می‌توانید از 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. استفاده از صفحه نشت فعالیت AHAT: AHAT شامل یک صفحه اختصاصی نشت فعالیت است که به‌طور خودکار نمونه‌های فعالیتی را که ازبین رفته‌اند اما هنوز در حافظه نگهداری می‌شوند شناسایی می‌کند. این روش اغلب سریع‌ترین راه برای پیدا کردن نشتی‌های رایج است.
  3. بررسی مسیر به ریشه‌های GC: برای هر شیء نشت‌شده، از نمای مسیر از ریشه در AHAT استفاده کنید تا دقیقاً بفهمید کدام مرجع آن را زنده نگه می‌دارد (برای مثال، یک فیلد ایستا، یک رشته طولانی‌مدت، یا یک شنونده ثبت‌شده).

← ابزارها | ↑ بالا | نگاشت‌های بیتی →