Nghiên cứu điển hình: Gỡ lỗi sự cố OOM

Sự cố hết bộ nhớ (OutOfMemoryError) xảy ra khi ứng dụng của bạn sử dụng hết vùng nhớ khối xếp Java được phân bổ và không thể thực hiện việc phân bổ bộ nhớ mới. Dấu vết ngăn xếp từ báo cáo sự cố OutOfMemoryError hiếm khi cho biết nguyên nhân gốc vì chúng chỉ cho thấy lệnh gọi phân bổ không thành công khi hết bộ nhớ, chứ không phải các đối tượng giữ lại phần còn lại của vùng nhớ khối xếp.

ProfilingManager cho phép bạn thu thập tệp báo lỗi trong Java từ các thiết bị tại chỗ và từ xa để có thể kiểm tra biểu đồ giữ lại đối tượng trong giao diện người dùng Perfetto và xác định chính xác đối tượng đang giữ bộ nhớ.

Trường hợp

Giả sử công cụ báo cáo sự cố của bạn gắn cờ một cụm OutOfMemoryError sự cố ngày càng tăng trong phiên bản chính thức khi người dùng tương tác với OomSampleActivity.

Để điều tra, bạn đăng ký một điều kiện kích hoạt TRIGGER_TYPE_OOM bằng ProfilingManager để hệ thống tự động ghi lại tệp báo lỗi khối xếp Java khi xảy ra OutOfMemoryError.

Ghi tệp báo lỗi

Trên Android 17 (cấp độ API 37) trở lên, để thu thập tệp báo lỗi khối xếp khi xảy ra sự cố OutOfMemoryError, hãy định cấu hình phân tích dựa trên điều kiện kích hoạt bằng ProfilingTrigger.TRIGGER_TYPE_OOM. Khi ứng dụng của bạn gửi một OutOfMemoryError chưa được xử lý, hệ thống sẽ tự động bắt đầu một bản ghi PROFILING_TYPE_JAVA_HEAP_DUMP mới.

Vì quy trình của ứng dụng sẽ chấm dứt khi xảy ra sự cố, nên hệ thống sẽ hoàn tất việc lưu tệp báo lỗi khối xếp trong nền và đưa kết quả vào hàng đợi (xem phần Vòng đời của trình kích hoạt). Đăng ký trình nghe kết quả chung trên nền Executor sau khi quá trình khởi động ứng dụng hoàn tất (để không làm chậm quá trình khởi động ứng dụng) bằng cách sử dụng registerForAllProfilingResults để ứng dụng của bạn nhận được ProfilingResult trong hàng đợi vào lần tiếp theo người dùng khởi chạy ứng dụng. Không giống như addProfilingTriggers (vẫn tồn tại sau khi khởi động lại ứng dụng và chỉ cần được gọi một lần hoặc khi cập nhật điều kiện kích hoạt), registerForAllProfilingResults phải được gọi mỗi khi khởi động ứng dụng:

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))
}

Java

@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));
}

Gỡ rối mã nguồn cho hồ sơ

Sau khi OutOfMemoryError kích hoạt một bản ghi và lệnh gọi lại của bạn nhận được ProfilingResult trong lần khởi chạy ứng dụng tiếp theo, hãy tải tệp hồ sơ lên máy chủ của bạn hoặc kéo tệp đó vào máy phát triển bằng adb. Nếu bạn thu thập hồ sơ từ một bản phát hành sử dụng tính năng rút gọn mã và làm rối mã nguồn R8, hãy gỡ rối mã nguồn đó khi không có mạng trên máy trạm hoặc máy chủ trước khi phân tích. Nếu không có tính năng gỡ rối mã nguồn, R8 sẽ làm rối mã nguồn tên lớp, trường và phương thức Java và Kotlin trong tệp báo lỗi khối xếp thô (ví dụ: chuyển tên lớp ban đầu thành a.b).

Sử dụng lệnh trace_processor bundle của Perfetto với cờ --proguard-map để đóng gói hồ sơ đã thu thập cùng với tệp R8 mapping.txt được tạo cho chính bản phát hành đó:

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

Lệnh này tạo ra deobfuscated_oom.tar, một tệp lưu trữ phong phú, gói hồ sơ gốc với các liên kết gỡ rối mã nguồn. Khi bạn mở bản lưu trữ này trong giao diện người dùng Perfetto hoặc truy vấn bằng trace_processor, Perfetto sẽ tự động khôi phục tất cả tên lớp, trường và phương thức ban đầu. Để biết thêm thông tin, hãy xem tài liệu về Ký hiệu hoá và gỡ rối mã nguồn của Perfetto.

Phân tích tệp báo lỗi

Bạn có thể kiểm tra tệp báo lỗi đã được gỡ rối mã nguồn theo cách thủ công trong giao diện người dùng Perfetto như trong các bước sau hoặc tự động điều tra OOM bằng một tác nhân AI.

Mở tệp báo cáo khối xếp đã được gỡ rối trong giao diện người dùng Perfetto rồi chọn Heapdump Explorer (Trình khám phá báo cáo khối xếp) trong thanh điều hướng bên trái. Trong thẻ Flamegraph (Biểu đồ dạng ngọn lửa) có Measure (Đo lường) được đặt thành Object Size (Kích thước đối tượng), hệ phân cấp giữ lại trong Hình 1 sẽ nổi bật ngay lập tức.

Biểu đồ dạng ngọn lửa của Trình khám phá tệp báo lỗi trong Giao diện người dùng Perfetto cho thấy StatsReporter giữ lại 8 thực thể của OomSampleActivity với tổng dung lượng là 240,28 MiB.
Hình 1. Biểu đồ dạng ngọn lửa của tệp báo lỗi cho thấy 8 thực thể được giữ lại của OomSampleActivity.

Khi kiểm tra biểu đồ dạng ngọn lửa trong Hình 1, bạn sẽ thấy một số kết quả chính:

  • Giữ lại bộ nhớ lớn: Trong tổng số vùng nhớ khối xếp 254,79 MiB (root), com.example.sampleapp.OomSampleActivity giữ lại kích thước Tích luỹ là 240,28 MiB (94,31% của toàn bộ vùng nhớ khối xếp ứng dụng), hầu hết nằm trong mảng con byte[].
  • Nhiều thực thể hoạt động trực tiếp: Khi chọn khung OomSampleActivity, bạn sẽ thấy Số lượng tự thân: 8. Mặc dù chỉ có một thực thể của OomSampleActivity tồn tại trên màn hình tại một thời điểm nhất định, nhưng 8 thực thể riêng biệt vẫn hoạt động đồng thời trong bộ nhớ.
  • Đường dẫn đối tượng giữ lại: Khi đọc các khung mẹ của OomSampleActivity trong biểu đồ dạng ngọn lửa, bạn sẽ thấy chuỗi tham chiếu giữ cho các thực thể đó hoạt động: com.example.sampleapp.StatsReporter giữ một java.util.concurrent.CopyOnWriteArrayList (được hỗ trợ bởi java.lang.Object[]), tham chiếu trực tiếp đến tất cả 8 thực thể OomSampleActivity.

Xác định nguyên nhân gốc trong mã

Với đường dẫn giữ lại trỏ từ StatsReporter đến OomSampleActivity, hãy kiểm tra cả hai lớp trong cơ sở mã của bạn.

StatsReporter là một singleton duy trì danh sách các lệnh gọi lại StatsListener và định kỳ đăng thông tin cập nhật từ một luồng thăm dò ý kiến trong nền:

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 }
        }
    }
}

Java

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();
    }
}

Tiếp theo, hãy kiểm tra 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"
    }
}

Java

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);
    }
}

Mã này xác nhận lý do StatsReporter giữ lại 8 thực thể của OomSampleActivity:

  1. Mỗi thực thể OomSampleActivity phân bổ một vùng đệm byte[] 30 MiB (heavyData trong Kotlin, mHeavyData trong Java) và tự đăng ký làm trình nghe trên singleton StatsReporter trong onCreate.
  2. OomSampleActivity không bao giờ gọi unregisterListener khi hệ thống huỷ nó. Vì StatsReporter tồn tại lâu hơn hoạt động và giữ tham chiếu mạnh đến mọi trình nghe đã đăng ký, nên trình thu gom rác không thể thu hồi các thực thể đã bị huỷ hoặc mảng byte[] 30 MiB của chúng.
  3. Mỗi lần hệ thống hủy bỏ và tạo lại OomSampleActivity, chẳng hạn như trong quá trình xoay màn hình trong vòng đời của activity Android, một thực thể 30 MiB khác sẽ bị rò rỉ trong StatsReporter. Sau 7 lần xoay (8 thực thể × 30 MiB ≈ 240 MiB), vùng nhớ khối xếp sẽ đầy và ứng dụng gặp sự cố với OutOfMemoryError.

Sửa chỗ rò rỉ

Để khắc phục lỗi rò rỉ bộ nhớ, hãy ghi đè onDestroy trong hoạt động và gọi StatsReporter.getInstance().unregisterListener(this) để singleton giải phóng tệp đối chiếu khi hệ thống huỷ hoạt động:

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"
    }
}

Java

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);
    }
}

Tự động hoá quy trình điều tra lỗi hết bộ nhớ bằng tác nhân AI

Thay vì kiểm tra biểu đồ hình ngọn lửa theo cách thủ công và theo dõi các tệp tham chiếu trên các tệp, bạn có thể sử dụng một tác nhân lập trình AI được định cấu hình bằng kỹ năng AI của Perfetto để phân tích tệp báo lỗi vùng nhớ khối xếp và liên kết tệp đó với toàn bộ mã nguồn của bạn:

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.

Tác nhân truy vấn tệp báo lỗi khối xếp bằng trace_processor, theo dõi đường dẫn của đối tượng chi phối từ StatsReporter đến các thực thể OomSampleActivity bị rò rỉ và kiểm tra cả hai lớp trong toàn bộ mã nguồn của bạn. Nếu không có sự can thiệp thủ công, công cụ này sẽ đưa ra cùng một kết luận, đó là OomSampleActivity đăng ký một trình nghe trong onCreate mà không huỷ đăng ký, đồng thời tạo một bản vá thêm unregisterListener(this) trong onDestroy (báo cáo sau đây được tạo từ một bản ghi riêng biệt có 7 thực thể được giữ lại tại thời điểm xảy ra sự cố):

# 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.

Xác minh kết quả khắc phục

Xác minh rằng lỗi rò rỉ đã được giải quyết trên thiết bị thử nghiệm hoặc trong môi trường thực tế.

Nếu có thể tái hiện vấn đề trên thiết bị của bạn

Vì TRIGGER_TYPE_OOM chỉ thu thập tệp báo lỗi khối xếp khi xảy ra OutOfMemoryError, nên việc khắc phục thành công sẽ ngăn trình kích hoạt kích hoạt. Để kiểm tra vùng nhớ khối xếp và xác nhận rằng không còn rò rỉ, hãy xoay màn hình rồi ghi lại tệp báo lỗi vùng nhớ khối xếp Java theo yêu cầu từ dòng lệnh:

  1. Tắt bộ giới hạn tốc độ (rate_limiter.disabled true) và giữ lại kết quả tạm thời (delete_temporary_results.disabled true) để hệ thống giữ lại kết quả chụp dòng lệnh trong /data/misc/perfetto-traces/profiling/.
  2. Mở OomSampleActivityFixed trên thiết bị kiểm thử, xoay màn hình ít nhất 8 lần để xác nhận rằng ứng dụng không còn gặp sự cố và giữ ứng dụng ở nền trước.
  3. Kích hoạt tệp báo lỗi Java theo yêu cầu (profiling_type 1) bằng lệnh shell cmd profiling_service request-profiling:

    adb shell cmd profiling_service request-profiling 1 com.example.sampleapp
    
  4. Kéo hồ sơ được giữ lại từ /data/misc/perfetto-traces/profiling/ và mở hồ sơ đó trong giao diện người dùng Perfetto.

Biểu đồ dạng ngọn lửa của Trình khám phá tệp báo lỗi trong giao diện người dùng Perfetto sau khi khắc phục, cho thấy một thực thể duy nhất của OomSampleActivityFixed giữ lại 30,01 MiB.
Hình 2. Biểu đồ hình ngọn lửa của tệp báo lỗi sau khi khắc phục cho thấy một thực thể duy nhất của OomSampleActivityFixed.

Như minh hoạ trong Hình 2, sau nhiều lần xoay màn hình:

  • com.example.sampleapp.OomSampleActivityFixed có Số lượng tự đếm là 1 thay vì tích luỹ một thực thể trên mỗi lần xoay.
  • Kích thước được giữ lại Tích luỹ của nó vẫn ổn định ở mức 30,01 MiB (trong tổng số vùng nhớ khối xếp 44,27 MiB), xác nhận rằng trình thu gom rác lấy lại các thực thể hoạt động trước đó.

Nếu vấn đề khó tái hiện trên máy

Khi thao tác điều hướng phức tạp trên nhiều màn hình, phiên hoạt động kéo dài hoặc dữ liệu người dùng cụ thể gây ra một OutOfMemoryError mà bạn không thể tái hiện một cách đáng tin cậy trên thiết bị thử nghiệm, hãy xác thực bản sửa lỗi trong phiên bản chính thức sau khi phát hành:

  • Theo dõi cụm sự cố OutOfMemoryError trong công cụ báo cáo sự cố dưới dạng chỉ số proxy trên các bản phát hành để xác nhận rằng các sự cố liên quan đến OomSampleActivity đã giảm.
  • Kiểm tra mọi tệp báo lỗi bộ nhớ TRIGGER_TYPE_OOM mới được thu thập từ bản phát hành mới để xác minh rằng StatsReporter không còn giữ lại các thực thể OomSampleActivity đã bị huỷ.