程序之間的記憶體配置

Android 平台執行時有個前提,那就是會持續浪費記憶體,並持續設法用掉所有可用的記憶體。舉例來說,系統會在應用程式關閉後將應用程式留在記憶體內,以便使用者快速切換回這些應用程式。也就是因為這樣,Android 裝置在執行時,通常不會發生大量記憶體未用到的情況。如要在重要系統程序和許多使用者應用程式間妥善配置記憶體,記憶體的管理工作就顯得非常重要。

本頁面會針對 Android 如何為系統和使用者應用程式配置記憶體,討論這方面的基礎概念,也會說明作業系統如何回應記憶體不足的狀況。

記憶體類型

Android 裝置內有三種不同類型的記憶體:RAM、zRAM 和儲存空間。請注意,CPU 和 GPU 皆存取同一個 RAM。

記憶體類型

圖 1 記憶體類型 - RAM、zRAM 和儲存空間

  • RAM 是最快速的記憶體,但是大小通常有限。高階裝置上的 RAM 數量通常最多。

  • zRAM 是 RAM 用來交換空間的分區。所有內容放入 zRAM 時都會壓縮,並且從 zRAM 複製時解壓縮。這部分的 RAM 會隨頁面進出 zRAM 而增大或縮小。裝置製造商可以設定大小上限。

  • 儲存空間內含所有持續性資料,如檔案系統和其中所有應用程式、程式庫及平台的物件程式碼。儲存空間的容量比其他兩種記憶體要大得多。在 Android 上,儲存空間不會像在其他 Linux 實作項目中用於交換空間,這是因為頻繁寫入可能會耗損記憶體,縮短儲存媒介的壽命。

記憶體頁面

RAM 可以分割成多個「頁面」。一般來說,每個頁面相等於 4 KB 的記憶體。

網頁將視為「免費」或「二手」。可用頁面就是未使用的 RAM。已使用的頁面則是系統目前使用的 RAM,並分為以下幾類:

  • 快取:由檔案在儲存空間裡支援的記憶體 (例如程式碼或記憶體對應檔案)。快取記憶體分為兩種類型:
    • 私人:由一個程序擁有,且未共用
      • 乾淨:儲存空間內未經修改的檔案副本,可由 kswapd 刪除,以便增加可用記憶體
      • 骯髒:儲存空間內修改過的檔案副本,可由 kswapd 移動或壓縮到 zRAM 內,以便增加可用記憶體
    • 共用:由多個程序使用
      • 乾淨:儲存空間內未經修改的檔案副本,可由 kswapd 刪除,以便增加可用記憶體
      • 骯髒:儲存空間內修改過的檔案副本;可由 kswapd 或明確使用 msync()munmap() 把變更內容寫回儲存空間內的檔案,以便增加可用記憶體
  • 匿名:由檔案在儲存空間中支援的記憶體 (例如由 mmap() 分配,且已設定 MAP_ANONYMOUS 旗標)
    • 骯髒:可由 kswapd 移動/壓縮到 zRAM 內,以便增加可用記憶體

隨著系統主動管理 RAM,可用和已使用的頁面比例也會隨之變化。如要管理記憶體不足的情況,務必瞭解本章節說明的概念。本文件下一章節將會詳細說明這些概念。

記憶體不足時的管理工作

Android 會透過兩種主要機制處理記憶體不足的情況,也就是核心交換 Daemon 和記憶體不足終止工具。

核心交換 Daemon

核心交換 Daemon (kswapd) 是 Linux kernel 的一部分,可以把已使用的記憶體轉換為可用記憶體。當裝置上的可用記憶體不足時,系統就會啟用 Daemon。Linux kernel 設有可用記憶體的高低門檻。一旦可用記憶體低於最低門檻,kswapd 就會開始取回記憶體。當可用記憶體達到最高門檻時,kswapd 則會停止取回記憶體。

kswapd 可以藉由刪除乾淨頁面取回乾淨頁面,因為這些頁面受到儲存空間支援,並未經過修改。當有程序嘗試處理已經刪除的乾淨頁面時,系統會從儲存空間複製頁面到 RAM。這項作業稱為「需求分頁」

已刪除由儲存空間支援的乾淨頁面

圖 2. 已刪除由儲存空間支援的乾淨頁面

kswapd 可以把快取的私有骯髒頁面和匿名骯髒頁面移動到 zRAM 內進行壓縮。這樣做即可在 RAM 釋出可用記憶體 (可用頁面)。如有程序嘗試使用 zRAM 內的骯髒頁面,系統就會將該頁面解壓縮並移回 RAM 內。如果系統已終止和某壓縮頁面相關聯的程序,則會從 zRAM 中刪除該頁面。

當可用記憶體數量低於特定門檻時,系統會開始終止程序。

移動到 zRAM 並進行壓縮的骯髒頁面

圖 3. 移動到 zRAM 並進行壓縮的骯髒頁面

記憶體不足終止工具

很多時候,kswapd 會無法為系統釋出足夠的記憶體。此時,系統會使用 onTrimMemory() 將記憶體不足的消息告知應用程式,並指出應該減少配置。如果空間不足,核心會開始終止程序,藉此釋出記憶體。為此,記憶體不足終止工具 (LMK) 便可派上用場。

LMK 會使用名為 oom_adj_score 的「記憶體不足」分數為執行中的程序排出優先順序,藉此決定要終止哪些程序。系統會優先終止分數較高的程序。背景應用程式的終止順位最高,系統程序的順位最低。下表由高到低列出 LMK 的分數類別。屬於第一列最高分類別的項目會最先遭到終止:

Android 程序,頂端為最高分項目

圖 4. Android 程序,頂端為最高分,而底部為最低分

上表各類別的說明如下:

  • 背景應用程式:先前執行但目前未使用的應用程式。 LMK 會先終止 oom_adj_score 最高的背景應用程式。

  • 先前的應用程式:最近使用的背景應用程式。先前的應用程式比背景應用程式優先順位更高 (分數較低),因為相較於背景應用程式,使用者更有可能切換回先前的應用程式。

  • Google Home 應用程式:啟動器應用程式。如果終止這個應用程式會導致桌布消失。

  • 服務:服務是由應用程式啟動,可能含有雲端同步處理或上傳作業。

  • 可察覺的應用程式:並非在前景執行,但使用者可藉由某些方式察覺的應用程式,這些方式包括執行會顯示小型 UI 的搜尋程序,或聽音樂。

  • 前景應用程式:目前正在使用的應用程式。如果終止前景應用程式,看起來就會像是應用程式當機,可能會讓使用者認為裝置發生問題。

  • 持續性 (服務):這些是裝置的核心服務,如電話通訊系統和 Wi-Fi。

  • 系統:系統程序。如果終止這些程序,可能會重新啟動手機。

  • 原生:由系統使用、非常低階的程序,例如 kswapd

裝置製造商可以變更 LMK 的行為。

計算記憶體用量

核心會追蹤系統中的所有記憶體頁面。

不同程序使用的頁面

圖 5. 不同程序使用的頁面

系統在判斷應用程式使用多少記憶體的時候,必須把共用頁面納入考量。由於存取同一服務或程式庫的應用程式會共用記憶體頁面,例如 Google Play 服務和某個遊戲應用程式可能會共用定位服務,因此很難判斷屬於整個服務和個別應用程式的記憶體各為多少。

由兩個應用程式共用的頁面

圖 6. 由兩個應用程式共用的頁面 (位於中央)

如果想判斷應用程式的記憶體用量,可以使用以下任一種指標:

  • 常設集大小 (RSS):該應用程式使用的共用和非共用頁面數量
  • 比例集大小 (PSS):該應用程式使用的非共用頁面數量,以及共用頁面的平均分佈情形 (例如在三個程序共享 3 MB 的情況下,每個程序的 PSS 為 1 MB)
  • 不重複集大小 (USS):該應用程式使用的非共用頁面數量 (不含共用頁面)

如果作業系統想瞭解所有程序共使用多少記憶體,PSS 就非常實用,因為這個指標不會重複計算頁面。但由於系統需要判斷哪些頁面經過共用,以及由多少程序共用,因此 PSS 需要的計算時間較長。相比之下,RSS 不會區別共用和非共用頁面,所以計算速度較快,較適合用來追蹤記憶體配置的變動情形。

其他資源