আপনার অ্যাপ বরাদ্দ করা জাভা হিপ শেষ করে ফেললে এবং নতুন মেমরি বরাদ্দ করতে না পারলে
মেমরি শেষ হয়ে যাওয়ার (OutOfMemoryError) কারণে ক্র্যাশ হয়। OutOfMemoryError ক্র্যাশ রিপোর্ট থেকে স্ট্যাক ট্রেস খুব কমই মূল কারণ প্রকাশ করে কারণ তারা
শুধুমাত্র মেমরি শেষ হয়ে গেলে ব্যর্থ হওয়া অ্যালোকেশন কল দেখায়, হিপের বাকি অংশ ধরে রাখা অবজেক্টগুলি নয়।
ProfilingManager আপনাকে ফিল্ডে এবং স্থানীয়ভাবে ডিভাইস থেকে জাভা হিপ ডাম্প ক্যাপচার করতে দেয় যাতে আপনি Perfetto
UI-এ অবজেক্ট রিটেনশন গ্রাফ পরীক্ষা করতে পারেন এবং মেমরি কী ধরে রাখছে তা পিনপয়েন্ট করতে পারেন।
পরিস্থিতি
ধরুন, আপনার ক্র্যাশ রিপোর্টিং টুল প্রোডাকশনে OutOfMemoryErrorটি ক্র্যাশের ক্রমবর্ধমান ক্লাস্টার ফ্ল্যাগ করে
যখন ব্যবহারকারীরা OomSampleActivity-এর সাথে ইন্টার্যাক্ট করেন।
তদন্ত করার জন্য, আপনি ProfilingManager-এর সাথে একটি TRIGGER_TYPE_OOM ট্রিগার রেজিস্টার করেন যাতে OutOfMemoryError হলে সিস্টেম অটোমেটিক জাভা হিপ ডাম্প ক্যাপচার করে।
হিপ ডাম্প ক্যাপচার করুন
Android 17 (API লেভেল 37) এবং তার পরবর্তী ভার্সনে,
OutOfMemoryError ক্র্যাশ হলে হিপ ডাম্প ক্যাপচার করতে, ProfilingTrigger.TRIGGER_TYPE_OOM-এর সাথে ট্রিগার-ভিত্তিক প্রোফাইলিং কনফিগার করুন। আপনার অ্যাপ কোনও আনহ্যান্ডেলড
OutOfMemoryError থ্রো করলে, সিস্টেম অটোমেটিক নতুন
PROFILING_TYPE_JAVA_HEAP_DUMP ক্যাপচার শুরু করে।
ক্র্যাশ হওয়ার কারণে অ্যাপ প্রসেস বন্ধ হয়ে যায়, তাই সিস্টেম ব্যাকগ্রাউন্ডে হিপ ডাম্প সেভ করা শেষ করে এবং ফলাফল সারিবদ্ধ করে (ট্রিগার লাইফসাইকেল দেখুন)। অ্যাপ স্টার্টআপ সম্পূর্ণ হওয়ার পরে (যাতে অ্যাপ স্টার্ট হতে দেরি না হয়)
registerForAllProfilingResults ব্যবহার করে ব্যাকগ্রাউন্ড Executor-এ
গ্লোবাল রেজাল্ট লিসেনার রেজিস্টার করুন, যাতে ব্যবহারকারী পরের বার অ্যাপ লঞ্চ করলে
আপনার অ্যাপ সারিবদ্ধ ProfilingResult পায়। addProfilingTriggers-এর (যা অ্যাপ রিস্টার্ট করার পরেও থেকে যায় এবং শুধুমাত্র একবার বা আপডেট ট্রিগার করার সময় কল করতে হয়) মতো না হয়ে, registerForAllProfilingResults-কে প্রতিটি অ্যাপ স্টার্টআপে কল করতে হবে:
Kotlin
@RequiresApi(Build.VERSION_CODES.CINNAMON_BUN) fun registerOomTrigger(context: Context) { val profilingManager = context.getSystemService(ProfilingManager::class.java) ?: return val callbackExecutor: Executor = Executors.newSingleThreadExecutor() val resultCallback = Consumer<ProfilingResult> { profilingResult -> if ( profilingResult.errorCode == ProfilingResult.ERROR_NONE && profilingResult.triggerType == ProfilingTrigger.TRIGGER_TYPE_OOM ) { val heapDumpPath = profilingResult.resultFilePath // Schedule a background job to compress and upload heapDumpPath } } profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback) val oomTrigger = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_OOM) .build() profilingManager.addProfilingTriggers(listOf(oomTrigger)) }
জাভা
@RequiresApi(Build.VERSION_CODES.CINNAMON_BUN) public static void registerOomTrigger(Context context) { ProfilingManager profilingManager = context.getSystemService(ProfilingManager.class); if (profilingManager == null) return; Executor callbackExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE && profilingResult.getTriggerType() == ProfilingTrigger.TRIGGER_TYPE_OOM) { String heapDumpPath = profilingResult.getResultFilePath(); // Schedule a background job to compress and upload heapDumpPath } }; profilingManager.registerForAllProfilingResults(callbackExecutor, resultCallback); ProfilingTrigger oomTrigger = new ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_OOM) .build(); profilingManager.addProfilingTriggers(Collections.singletonList(oomTrigger)); }
প্রোফাইল ডিঅবফাসকেট করুন
OutOfMemoryError ক্যাপচার ট্রিগার করার পরে এবং আপনার কলব্যাক পরবর্তী অ্যাপ লঞ্চে
ProfilingResult পেলে, প্রোফাইল ফাইলটি আপনার সার্ভারে আপলোড করুন
অথবা adb ব্যবহার করে আপনার ডেভেলপমেন্ট মেশিনে এটি পুল করুন। আপনি যদি রিলিজ বিল্ড থেকে প্রোফাইল ক্যাপচার করে থাকেন, যা R8 কোড সঙ্কোচন এবং
অবফাসকেশন ব্যবহার করে, তাহলে বিশ্লেষণের আগে আপনার ওয়ার্কস্টেশন বা সার্ভারে এটি অফলাইনে ডিঅবফাসকেট করুন। ডিঅবফাসকেশন ছাড়া, R8 জাভা এবং Kotlin ক্লাস, ফিল্ড,
এবং র হিপ ডাম্পে মেথডের নাম অবফাসকেট করে (যেমন, আসল ক্লাসের নাম
a.b-এ পরিবর্তন করা)।
Perfetto-এর trace_processor bundle কমান্ডের সাথে --proguard-map
ফ্ল্যাগ ব্যবহার করে ক্যাপচার করা প্রোফাইলকে সেই নির্দিষ্ট রিলিজ বিল্ডের জন্য তৈরি করা R8 mapping.txt ফাইলের সাথে
প্যাকেজ করুন:
trace_processor bundle \
--proguard-map /path/to/mapping.txt \
profile_trigger-type-7_2026-05-06-14-12-40-748_uid-10316.perfetto-java-heap-dump \
deobfuscated_oom.tar
এই কমান্ড deobfuscated_oom.tar তৈরি করে, এটি একটি সমৃদ্ধ আর্কাইভ যা
আসল প্রোফাইলকে তার ডিঅবফাসকেশন ম্যাপিংয়ের সাথে বান্ডিল করে। Perfetto UI-তে এই আর্কাইভ খুললে
বা trace_processor-এর মাধ্যমে কোয়েরি করলে, Perfetto অটোমেটিক
সব আসল ক্লাস, ফিল্ড ও পদ্ধতির নাম ফিরিয়ে আনে। আরও তথ্যের জন্য, Perfetto সিম্বোলাইজেশন ও ডিঅবফাসকেশন ডকুমেন্টেশন দেখুন।
হিপ ডাম্প বিশ্লেষণ করুন
আপনি নিম্নলিখিত ধাপগুলি অনুসরণ করে Perfetto UI-তে ডিঅবফাসকেট করা হিপ ডাম্প ম্যানুয়ালি পরীক্ষা করতে পারেন অথবা AI এজেন্টের সাহায্যে OOM সংক্রান্ত তদন্ত অটোমেট করতে পারেন।
Perfetto UI-তে ডিঅবফাসকেট করা হিপ ডাম্প খুলুন এবং বাঁদিকের নেভিগেশন বারে হিপডাম্প এক্সপ্লোরার বেছে নিন। Flamegraph ট্যাবে Measure বিকল্প Object Size-এ সেট করা থাকলে, ১ নম্বর ছবিতে রিটেনশন হায়ারার্কি তৎক্ষণাৎ আলাদাভাবে বোঝা যায়।
OomSampleActivity-এর ৮টি রিটেন করা ইনস্ট্যান্স দেখানো হচ্ছে।ছবি ১-এ ফ্লেম গ্রাফটি পরীক্ষা করলে বেশ কিছু গুরুত্বপূর্ণ তথ্য জানা যায়:
- প্রচুর মেমরি রিটেনশন: মোট ২৫৪.৭৯ MiB হিপ (
root) থেকে,com.example.sampleapp.OomSampleActivity২৪০.২৮ MiB (সম্পূর্ণ অ্যাপ হিপের ৯৪.৩১%) ক্রমবর্ধমান সাইজ রিটেন করে, যার বেশিরভাগই চাইল্ডbyte[]অ্যারেতে থাকে। - একাধিক লাইভ অ্যাক্টিভিটি ইনস্ট্যান্স:
OomSampleActivityফ্রেম বেছে নিলে সেল্ফ কাউন্ট: ৮ দেখায়। যদিও যেকোনও সময়ে স্ক্রিনেOomSampleActivity-এর শুধু একটি ইনস্ট্যান্স থাকা উচিত, মেমরিতে একই সাথে ৮টি আলাদা ইনস্ট্যান্স লাইভ আছে। - রিটেনিং অবজেক্ট পাথ: ফ্লেম গ্রাফে
OomSampleActivity-এর পেরেন্ট ফ্রেম পড়লে, রেফারেন্স চেইন দেখা যায়, যা এইসব ইনস্ট্যান্সকে জীবিত রাখে:com.example.sampleapp.StatsReporter-এ একটিjava.util.concurrent.CopyOnWriteArrayList(java.lang.Object[]-এর মাধ্যমে ব্যাক-আপ নেওয়া) আছে, যা সরাসরি সব ৮টিOomSampleActivityইনস্ট্যান্সকে রেফারেন্স করে।
কোডে মূল কারণ শনাক্ত করো
StatsReporter থেকে OomSampleActivity-এ রিটেনশন পাথ পয়েন্ট করার মাধ্যমে,
আপনার কোডবেসে দুটি ক্লাসই পরীক্ষা করুন।
StatsReporter হল একটি সিঙ্গলটন যা StatsListener কলব্যাকের তালিকা বজায় রাখে এবং
ব্যাকগ্রাউন্ড পোলিং থ্রেড থেকে নির্দিষ্ট সময় অন্তর আপডেট পোস্ট করে:
Kotlin
class StatsReporter private constructor() { fun interface StatsListener { fun onReportStats(stats: String) } private val listeners: MutableList<StatsListener> = CopyOnWriteArrayList() private var isReporting = false init { startReporting() } fun registerListener(listener: StatsListener) { if (!listeners.contains(listener)) { listeners.add(listener) } } fun unregisterListener(listener: StatsListener) { listeners.remove(listener) } private fun startReporting() { if (isReporting) return isReporting = true Thread({ while (true) { try { // Poll every 10 seconds Thread.sleep(10000) } catch (e: InterruptedException) { Thread.currentThread().interrupt() break } for (listener in listeners) { // Send periodic mock stats update from the background thread listener.onReportStats("Update from StatsReporter background polling thread") } } }, "StatsReporterPollingThread").start() } companion object { private var instance: StatsReporter? = null @JvmStatic fun getInstance(): StatsReporter { return instance ?: StatsReporter().also { instance = it } } } }
জাভা
public class StatsReporter { public interface StatsListener { void onReportStats(String stats); } private static StatsReporter sInstance; private final List<StatsListener> mListeners = new CopyOnWriteArrayList<>(); private boolean mIsReporting = false; private StatsReporter() { startReporting(); } public static StatsReporter getInstance() { if (sInstance == null) { sInstance = new StatsReporter(); } return sInstance; } public void registerListener(StatsListener listener) { if (!mListeners.contains(listener)) { mListeners.add(listener); } } public void unregisterListener(StatsListener listener) { mListeners.remove(listener); } private void startReporting() { if (mIsReporting) return; mIsReporting = true; new Thread(() -> { while (true) { try { // Poll every 10 seconds Thread.sleep(10000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } for (StatsListener listener : mListeners) { // Send periodic mock stats update from the background thread listener.onReportStats("Update from StatsReporter background polling thread"); } } }, "StatsReporterPollingThread").start(); } }
এরপর, OomSampleActivity পরীক্ষা করুন:
Kotlin
class OomSampleActivity : Activity(), StatsReporter.StatsListener { // Allocate some memory. private val heavyData = ByteArray(30 * 1024 * 1024) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_oom_sample) StatsReporter.getInstance().registerListener(this) } override fun onReportStats(stats: String) { // Read from heavyData so R8 doesn't strip it out as dead code during release builds Log.d(TAG, "Received stats in heavy leaked activity: $stats | Data size: ${heavyData.size}") } companion object { private const val TAG = "OomSampleActivity" } }
জাভা
public class OomSampleActivity extends Activity implements StatsReporter.StatsListener { private static final String TAG = "OomSampleActivity"; // Allocate some memory. private final byte[] mHeavyData = new byte[30 * 1024 * 1024]; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_oom_sample); StatsReporter.getInstance().registerListener(this); } @Override public void onReportStats(String stats) { // Read from mHeavyData so R8 does not strip it out as dead code during release builds Log.d(TAG, "Received stats in heavy leaked activity: " + stats + " | Data size: " + mHeavyData.length); } }
কোডটি নিশ্চিত করে যে কেন StatsReporter OomSampleActivity-এর ৮টি ইনস্ট্যান্স
ধরে রাখে:
- প্রতিটি
OomSampleActivityইনস্ট্যান্স একটি ৩০ MiBbyte[]বাফার বরাদ্দ করে (Kotlin-এheavyData, Java-তেmHeavyData) এবংonCreate-এ সিঙ্গলটনStatsReporter-এ লিসেনার হিসেবে নিজেকে রেজিস্টার করে। - সিস্টেম
unregisterListenerধ্বংস করার সময়OomSampleActivityকখনও কল করে না। যেহেতুStatsReporterঅ্যাক্টিভিটির চেয়ে বেশি সময় ধরে চলে এবং প্রতিটি রেজিস্টার করা লিসেনারের স্ট্রং রেফারেন্স ধরে রাখে, তাই গার্বেজ কালেক্টর নষ্ট হয়ে যাওয়া ইনস্ট্যান্স বা তার ৩০ MiBbyte[]অ্যারে রিক্লেম করতে পারে না। - প্রতিবার সিস্টেম
OomSampleActivityধ্বংস করে এবং আবার তৈরি করে, যেমন Android অ্যাক্টিভিটি লাইফসাইকেলে স্ক্রিন ঘোরানোর সময়, অন্য 30 MiB ইনস্ট্যান্সStatsReporter-এ লিক হয়। ৭ বার রোটেশনের পরে (৮টি ইনস্ট্যান্স × ৩০ MiB ≈ ২৪০ MiB), হিপ ভর্তি হয়ে যায় এবং অ্যাপ ক্র্যাশ করেOutOfMemoryErrorদেখায়।
লিক মেরামত করুন
মেমরি লিক সংক্রান্ত সমস্যার সমাধান করতে, অ্যাক্টিভিটিতে onDestroy ওভাররাইড করুন এবং
StatsReporter.getInstance().unregisterListener(this) কল করুন যাতে সিস্টেম অ্যাক্টিভিটি নষ্ট করার সময় সিঙ্গেলটন
তার রেফারেন্স রিলিজ করে:
Kotlin
class OomSampleActivityFixed : Activity(), StatsReporter.StatsListener { // Allocate some memory. private val heavyData = ByteArray(30 * 1024 * 1024) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_oom_sample) // Register the activity as a listener StatsReporter.getInstance().registerListener(this) } override fun onDestroy() { super.onDestroy() // FIX: Unregister the listener to prevent the memory leak // when the Activity is destroyed (for example, during screen rotation). StatsReporter.getInstance().unregisterListener(this) } override fun onReportStats(stats: String) { // Read from heavyData so R8 doesn't strip it out as dead code during release builds Log.d(TAG, "Received stats in fixed heavy activity: $stats | Data size: ${heavyData.size}") } companion object { private const val TAG = "OomSampleFixed" } }
জাভা
public class OomSampleActivityFixed extends Activity implements StatsReporter.StatsListener { private static final String TAG = "OomSampleFixed"; // Allocate some memory. private final byte[] mHeavyData = new byte[30 * 1024 * 1024]; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_oom_sample); // Register the activity as a listener StatsReporter.getInstance().registerListener(this); } @Override protected void onDestroy() { super.onDestroy(); // FIX: Properly unregister the listener to prevent the memory leak // when the Activity is destroyed (e.g., during screen rotation). StatsReporter.getInstance().unregisterListener(this); } @Override public void onReportStats(String stats) { // Read from mHeavyData so R8 does not strip it out as dead code during release builds Log.d(TAG, "Received stats in fixed heavy activity: " + stats + " | Data size: " + mHeavyData.length); } }
AI এজেন্টের মাধ্যমে OOM তদন্ত অটোমেট করুন
ফ্লেম গ্রাফ ম্যানুয়ালি পরীক্ষা করা এবং ফাইল জুড়ে রেফারেন্স ট্রেস করার পরিবর্তে, আপনি হিপ ডাম্প বিশ্লেষণ করতে এবং আপনার কোডবেসের সাথে এটি সম্পর্কযুক্ত করতে Perfetto AI দক্ষতা সহ কনফিগার করা একটি AI কোডিং এজেন্ট ব্যবহার করতে পারেন:
Analyze the Perfetto OOM heap dump at </path/to/profile>, which corresponds to
the codebase located at </path/to/codebase> using the Perfetto AI skill and find
the root cause of the OutOfMemoryError.
এজেন্ট trace_processor-এর মাধ্যমে হিপ ডাম্প কোয়েরি করে, StatsReporter থেকে লিক হওয়া OomSampleActivity ইনস্ট্যান্স পর্যন্ত ডমিনেটর
পাথ ট্রেস করে এবং
আপনার কোডবেসে দুটি ক্লাসই পরীক্ষা করে। ম্যানুয়াল হস্তক্ষেপ ছাড়াই, এটি একই সিদ্ধান্তে
পৌঁছায়, যে OomSampleActivity, onCreate-এ একটি লিসেনার রেজিস্টার করে
এটি আনরেজিস্টার না করেই এবং একটি প্যাচ জেনারেট করে যা
onDestroy-এ unregisterListener(this) যোগ করে (নিম্নলিখিত রিপোর্টটি একটি আলাদা ক্যাপচার থেকে
জেনারেট করা হয়েছে যাতে ক্র্যাশ হওয়ার সময় ৭টি রিটেন করা ইনস্ট্যান্স ছিল):
# Perfetto OOM Heap Dump Analysis Report
## 1. Orienting Context & First-Pass Triage
| Property | Value |
| :--- | :--- |
| **Process Name** | `com.example.sampleapp` |
| **Sample Timestamp (`graph_sample_ts`)** | `3433221241585` |
| **Leaf Retained Class** | `byte[]` (7 instances) |
| **Leaf Self Size** | `220,201,044` bytes (~210 MiB) |
### Dominator Tree Retention Path
Querying the `android.memory.heap_graph.dominator_tree` standard library module identifies the following largest dominated class path from GC root to leaf allocation:
[ROOT_JAVA_FRAME] com.example.sampleapp.StatsReporter [1]
-> java.util.concurrent.CopyOnWriteArrayList [1]
-> java.lang.Object[] [1]
-> com.example.sampleapp.OomSampleActivity [7]
-> byte[] [7] (220,201,044 bytes)
## 2. Root Cause Analysis
> **Important:** The `OutOfMemoryError` is caused by an **Activity listener leak**: `OomSampleActivity` (`app/src/main/java/com/example/sampleapp/OomSampleActivity.java#L8-L27`) registers itself as a listener on the long-lived `StatsReporter` (`app/src/main/java/com/example/sampleapp/StatsReporter.java#L6-L61`) singleton in `onCreate()`, but never unregisters itself in `onDestroy()`.
### Step-by-Step Retention Mechanism
1. **Per-Activity 30 MiB Allocation:**
Each instance of `OomSampleActivity` (`OomSampleActivity.java#L11-L12`) allocates a 30 MiB byte array (`30 * 1024 * 1024 = 31,457,280` bytes):
```java
// OomSampleActivity.java:12
private final byte[] mHeavyData = new byte[30 * 1024 * 1024];
```
2. **Strong Reference Registration in `onCreate()`:**
When created, `OomSampleActivity.onCreate()` (`OomSampleActivity.java#L15-L20`) registers `this` with `StatsReporter` (`StatsReporter.java#L29-L33`):
```java
// OomSampleActivity.java:19
StatsReporter.getInstance().registerListener(this);
```
3. **GC Root Pinning via Background Thread & Singleton:**
`StatsReporter` (`StatsReporter.java#L11-L60`) is both a static singleton (`sInstance`) and the owner of an active background thread (`StatsReporterPollingThread`, which holds a `[ROOT_JAVA_FRAME]` GC root reference while sleeping/looping in `startReporting()`). It stores listeners in `mListeners` (`CopyOnWriteArrayList<StatsListener>`).
4. **Leak Accumulation Across Activity Recreations:**
Because `OomSampleActivity` does not override `onDestroy()` to call `StatsReporter.unregisterListener(this)`, every destroyed instance of `OomSampleActivity` remains strongly reachable in `mListeners`. After 7 Activity creations (7 * 30 MiB = 220,200,960 bytes plus array headers = `220,201,044` bytes), the Java heap is exhausted.
---
## 3. Actionable Implementation Plan
### 1. Unregister the Listener in `OomSampleActivity.onDestroy()`
Update `OomSampleActivity.java` to unregister the listener when the `Activity` is destroyed (matching the fix in `OomSampleActivityFixed.java`):
```diff
--- a/app/src/main/java/com/example/sampleapp/OomSampleActivity.java
+++ b/app/src/main/java/com/example/sampleapp/OomSampleActivity.java
@@ -19,4 +19,10 @@
StatsReporter.getInstance().registerListener(this);
}
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ StatsReporter.getInstance().unregisterListener(this);
+ }
+
@Override
```
### 2. Optional Architectural Hardening in `StatsReporter`
* **Lifecycle-Aware Registration:** Have `StatsReporter.registerListener()` accept a `LifecycleOwner` and attach a `DefaultLifecycleObserver` that automatically invokes `unregisterListener()` on `ON_DESTROY`.
* **Weak References:** Store `WeakReference<StatsListener>` entries in `StatsReporter` and evict cleared references during iteration so forgotten `unregisterListener()` calls cannot retain entire `Activity` hierarchies.সমাধান যাচাই করুন
টেস্ট ডিভাইসে লোকালভাবে বা প্রোডাকশনে লিকটি সমাধান করা হয়েছে কিনা তা যাচাই করুন।
সমস্যাটি স্থানীয়ভাবে নকল করা গেলে
কারণ TRIGGER_TYPE_OOM শুধুমাত্র তখনই হিপ ডাম্প ক্যাপচার করে যখন OutOfMemoryError
ঘটে, একটি সফল সমাধান ট্রিগারকে ফায়ার করা থেকে বাধা দেয়। হিপ পরীক্ষা করতে
এবং লিক যে আর নেই তা কনফার্ম করতে, স্ক্রিন রোটেট করুন এবং তারপর কমান্ড লাইন থেকে অন-ডিমান্ড
জাভা হিপ ডাম্প ক্যাপচার করুন:
- রেট লিমিটার বন্ধ করুন (
rate_limiter.disabled true) এবং সাময়িক ফলাফল রেখে দিন (delete_temporary_results.disabled true) যাতে সিস্টেম কমান্ড-লাইন ক্যাপচার/data/misc/perfetto-traces/profiling/-এ রাখে। - আপনার টেস্ট ডিভাইসে
OomSampleActivityFixedখুলুন, অ্যাপটি আর ক্র্যাশ করছে না তা কনফার্ম করতে স্ক্রিনটি ৮ বা তার বেশি বার ঘোরান এবং এটি ফোরগ্রাউন্ডে রাখুন। cmd profiling_service request-profilingশেল কমান্ড ব্যবহার করে অন-ডিমান্ড জাভা হিপ ডাম্প (profiling_type1) ট্রিগার করুন:adb shell cmd profiling_service request-profiling 1 com.example.sampleapp/data/misc/perfetto-traces/profiling/থেকে রিটেন করা প্রোফাইল পুল করুন এবং Perfetto UI-তে এটি খুলুন।
OomSampleActivityFixed-এর একটি ইনস্ট্যান্স দেখা যাচ্ছে।ছবি ২-এ যেমন দেখানো হয়েছে, একাধিকবার স্ক্রিন ঘোরানোর পরে:
- প্রতিটি রোটেশনে একটি ইনস্ট্যান্স জমা করার পরিবর্তে
com.example.sampleapp.OomSampleActivityFixed-এর সেল্ফ কাউন্ট ১ আছে। - এটির কিউমুলেটিভ রেখে দেওয়া সাইজ ৩০.০১ MiB (মোট হিপ ৪৪.২৭ MiB-এর মধ্যে) হিসেবে স্থির থাকে, যা নিশ্চিত করে যে গার্বেজ কালেক্টর আগের অ্যাক্টিভিটি ইনস্ট্যান্স পুনরুদ্ধার করে।
সমস্যাটি স্থানীয়ভাবে নকল করা কঠিন হলে
জটিল মাল্টি-স্ক্রিন নেভিগেশন, দীর্ঘস্থায়ী সেশন বা নির্দিষ্ট ব্যবহারকারীর ডেটার কারণে
এমন OutOfMemoryError হলে যা আপনি টেস্ট ডিভাইসে নির্ভরযোগ্যভাবে রিপ্রোডিউস করতে পারবেন না,
রোল-আউটের পরে প্রোডাকশনে সমাধানটি যাচাই করুন:
- আপনার ক্র্যাশ রিপোর্টিং টুলে
OutOfMemoryErrorক্র্যাশ ক্লাস্টার মনিটর করুন রিলিজ জুড়ে প্রক্সি মেট্রিক হিসেবে যাতে কনফার্ম করা যায় যেOomSampleActivity-এর সাথে যুক্ত ক্র্যাশ কমে গেছে। - আপডেট করা রিলিজ থেকে সংগ্রহ করা যেকোনও নতুন
TRIGGER_TYPE_OOMহিপ ডাম্প পরীক্ষা করে দেখুন যাতেStatsReporterআর নষ্ট হয়ে যাওয়াOomSampleActivityইনস্ট্যান্স ধরে না রাখে।