執行緒和記憶體

應用程式每次建立新執行緒時,作業系統都必須分配記憶體來支援。雖然相較於完整程序,執行緒通常被視為「輕量級」,但記憶體成本並非零。在 Android 上,執行緒流失是很常見的記憶體流失方式。

執行緒的成本

在 Android 上建立執行緒時,有兩個主要元件會耗用記憶體:

  1. 原生堆疊pthread 程式庫會為執行緒分配堆疊,以儲存本機變數和呼叫框架。在 Android 上,這通常是預設的 1 MB。
  2. 內部 JVM/核心結構:ART 和 Linux 核心必須分配資料結構 (例如 pthread_internal_tjava.lang.Thread 物件),才能追蹤及管理執行緒。

堆疊記憶體和 mmap

請務必瞭解 Android 會使用 mmapMAP_ANONYMOUS | MAP_NORESERVE 標記,配置 1 MB 的原生堆疊。

也就是說,OS 會保留 1 MB 的虛擬記憶體位址空間,但不會立即分配 1 MB 的實體 RAM。只有在執行緒實際執行程式碼並將資料推送到堆疊時,才會分配實體記憶體 (分頁)。如果執行緒沒有任何動作,堆疊會耗用極少的實體 RAM。

實作練習:執行緒洩漏

我們將使用 MemoryLab 範例應用程式,觀察應用程式建立數百個閒置執行緒時會發生什麼情況。

1. 推出和基準

啟動應用程式並讀取基準記憶體:

adb shell am start -W -n com.android.memorylab/.MainActivity
adb shell dumpsys meminfo -s com.android.memorylab

查看輸出內容中的 StackNative Heap 列。這些檔案通常很小。

2. 建立 100 個執行緒

輕觸「建立 100 則討論串」按鈕。應用程式會產生 100 個新執行緒。為確保堆疊記憶體實際分頁到實體 RAM (因此我們可以在工具中看到),每個執行緒都會在堆疊上分配並寫入 10 KB 的小型陣列,然後開始等待。

在執行緒存留期間再次執行 meminfo 指令:

adb shell dumpsys meminfo -s com.android.memorylab

結果:

比較新輸出內容與基準。您會發現,在執行緒存留期間,以下指標會大幅增加:

  • Stack:增加 1 MB 以上。這代表為 100 個執行緒堆疊分配的實體頁面 (每個堆疊至少觸及 10 KB)。
  • Native Heap / Private Other:由於系統為每個執行緒分配的內部 pthread 結構增加,因此這項值會隨之增加。

如要清理,請輕觸「Destroy All Threads」(銷毀所有執行緒) 按鈕。

3. 在 Perfetto 中查看執行緒

Perfetto 非常適合用來追蹤執行緒生命週期和一段時間內的計數。

Perfetto 使用者介面螢幕截圖,顯示計數器軌跡,以及 memorylab 應用程式中啟動和結束的執行緒數量

使用 PerfettoSQL 進行執行緒分析

您可以查詢追蹤記錄,準確計算應用程式產生的執行緒數量:

SELECT p.name AS process_name, COUNT(t.id) as thread_count
FROM thread t JOIN process p USING (upid)
WHERE p.name = 'com.android.memorylab' AND t.name LIKE 'LeakedThread-%';

這類查詢可用於產生偵錯軌,並顯示計數器,如螢幕截圖所示。


← 應用程式代碼 | ↑ 向上 | 地區 →