記憶體管理總覽

Android 執行階段 (ART) 和 Dalvik 虛擬機器會利用分頁記憶體對應 (mmapping) 等方式管理記憶體。這表示任何經過應用程式修改的應用程式 (無論是配置新物件或觸及記憶體對應的頁面),都會繼續留在 RAM 內,無法分頁出去。如果想從應用程式釋放記憶體,唯一的辦法就是釋放應用程式保留的物件參照,藉此讓垃圾收集器取得這些記憶體。但是有一個例外:如果系統想再其他地方使用這些記憶體,那任何經過記憶體對應且並未修改的檔案 (例如程式碼) 都可以分頁到 RAM 外面。

本頁面將說明 Android 如何管理應用程式程序和記憶體配置。如要進一步瞭解如何更有效管理應用程式記憶體,請參閱「管理應用程式的記憶體」。

垃圾收集

在 Android 執行階段或 Dalvik 虛擬機器等受管理的記憶體環境中,系統會記錄每個記憶體配置。當系統判斷某段記憶體已經沒有程式使用時,就會將記憶體釋放回堆積中,不需程式設計人員自行操作。這個在受管理記憶體環境中收回未使用記憶體的機制,稱為「垃圾收集」。垃圾收集機制有兩大目標:找出程式中之後不供存取的資料物件,以及收回這些物件使用的資源。

Android 的記憶體堆疊分為不同代,也就是說,系統會根據所配置物件的預期生命和大小,追蹤不同的配置值區。舉例來說,最近配置的物件屬於「年輕代」。當物件保持使用時間足夠時,可將物件升級至較舊產生版本,之後再永久產生物件。

每個堆疊代都設有記憶體數量上限,物件只能佔用上限以內的記憶體。如果某一代即將達到上限,系統就會執行垃圾收集事件,嘗試釋放記憶體。垃圾收集時間長度取決於系統要收集的物件代,以及各代的活躍物件數量。

雖然垃圾收集的速度可能相當快,但仍可能影響應用程式效能。一般來說,您無法在程式碼中控制垃圾收集事件的發生時機。系統會根據一系列的條件,決定何時該執行垃圾收集事件。如果符合條件,系統就會停止執行程序,並開始垃圾收集。如果垃圾收集事件發生在動畫或音樂播放等緊密的處理迴圈中,就有可能拉長處理時間。時間拉長後,就可能導致應用程式的程式碼執行作業超過建議的 16 毫秒門檻,造成影格轉譯效率或順暢度不足。

另外,程式碼流程可能會執行迫使垃圾收集事件更頻繁發生的工作,或導致垃圾收集時間比平常更長。舉例來說,如果在 Alpha 混合動畫每個影格的 for 迴圈最內層配置多個物件,就可能因大量物件對記憶體堆積造成汙染。在這種情況下,垃圾收集器會執行多個垃圾收集事件,可能導致應用程式效能下降。

如果想進一步瞭解垃圾收集,請參閱「垃圾收集」。

分享回憶集錦

為了透過 RAM 處理所有需要的項目,Android 會嘗試在多個程序之間分享 RAM 頁面。執行方法如下:

  • 每個應用程式程序都從現有程序 Zygote 中建立分支。當系統啟動,並載入常見架構程式碼及活動主題等資源時,Zygote 程序就會啟動。為了開始新的應用程式程序,系統會為 Zygote 程序建立分支,然後在新程序中載入並執行應用程式程式碼。這個方法可以為架構程式碼和資源配置大部分的 RAM 頁面,讓所有應用程式程序共享這些頁面。
  • 大部分的靜態資料會對應至一個程序。這項技術可讓不同的程序共享資料,也能視需要將資料分頁。靜態資料的範例有:Dalvik 程式碼 (放在預先連結的 .odex 檔案裡以便直接進行記憶體對應)、應用程式資源 (把資源表格設為可以記憶體對應的結構,以及對齊 APK 的 Zip 項目),和傳統專案元素 (例如 .so 檔案裡的原生程式碼)。
  • 在許多情況下,Android 會明確使用配置的共用記憶體區域 (ashmem 或 gralloc),在不同程序之間分享相同的動態 RAM。舉例來說,視窗表層會使用應用程式和畫面合成器之間的共享記憶體,而遊標緩衝區會使用內容供應器和用戶端之間的共享記憶體。

由於共享記憶體的用途相當豐富,請務必小心決定應用程式要使用多少記憶體。如要瞭解可妥善決定應用程式記憶體用量的技術,請參閱有關調查 RAM 用量的說明文章。

配置及收回應用程式記憶體

在 Dalvik 堆積中,每個應用程式程序只能使用單一虛擬記憶體範圍。這會定義堆積的邏輯大小,這個大小可隨需求擴增,但需要遵守系統為每個應用程式定義的上限。

堆積的邏輯大小和堆積所使用的實體記憶體數量並不相同。Android 在檢查應用程式的堆積時,會運算出稱為「比例集大小」(PSS) 的值,這個值代表與其他程序共享的骯髒頁面和乾淨頁面,但只會根據共享該 RAM 的應用程式數量按比例計算。系統會將這項 (PSS) 總數視為實體記憶體用量。如果想進一步瞭解 PSS,請參閱有關調查 RAM 用量的指南。

Dalvik 堆積不會壓縮堆積的邏輯大小。也就是說,Android 不會透過重組堆積來凝聚空間。Android 只能在堆積末端出現未使用的空間時,縮減堆積的邏輯大小。不過,系統仍可縮減堆積所使用的實體記憶體。完成垃圾收集後,Dalvik 會逐步檢查堆積並找出未使用的頁面,然後使用 madvise 將這些頁面傳回核心。因此,為大型區塊建立配對的配置和取消配置,應可收回所有 (或幾乎所有) 使用的實體記憶體。不過,如果想從小型配置收回記憶體,效率可能會較低,因為小型配置可能仍與其他尚未釋放的項目共用頁面。

限制應用程式記憶體

為了維持正常運作的多工處理環境,Android 會為每個應用程式的堆積大小設定硬性限制。視裝置能夠提供的整體 RAM 數量而定,裝置的實際堆積大小限制會有所不同。如果應用程式已達到堆積容量上限,並嘗試配置更多記憶體,就可能會收到 OutOfMemoryError

在某些情況下,例如為了找出快取可安全保留多少資料,您可能會想查詢系統,找出目前裝置確切可用的堆積空間。您可以呼叫 getMemoryClass(),向系統查詢這項數值。這個方法會傳回整數,代表應用程式堆積可使用多少 MB。

切換應用程式

當使用者切換應用程式時,Android 會在快取裡保留非前景的應用程式 (也就是使用者看不到的應用程式,或並未執行播放音樂等前景服務的應用程式)。舉例來說,使用者首次啟動應用程式時,系統會為應用程式建立程序,但當使用者退出應用程式時,系統並「不會」退出程序,而會快取該程序。如果使用者稍後返回這個應用程式,系統就會重新利用該程序,藉此加快應用程式切換速度。

如果應用程式具有快取程序,且會保留目前不需要的資源,那麼即便使用者並非在使用應用程式,仍會影響系統的整體效能。當記憶體等資源逐漸不足,系統就會終止快取中的程序。系統也會處理佔用最多記憶體的程序,並可能終止這些程序來釋放 RAM。

注意:應用程式在快取中消耗的記憶體越少,就越不會遭到系統終止,且可以快速恢復運作。但視瞬間的系統需求而定,系統隨時可能終止快取的程序,無論資源用量多寡都一樣。

如果想進一步瞭解程序不在前景執行時的快取方式,以及 Android 如何決定要終止的程序,請參閱「處理程序和執行緒」指南。

記憶體壓力測試

雖然高階裝置的記憶體壓力問題較為罕見,但仍可能導致 RAM 較低的裝置 (例如搭載 Android (Go 版本) 的裝置) 的使用者遇到問題。請務必嘗試重現這個記憶體壓力環境,以便您可以撰寫檢測設備測試,從而驗證應用程式行為,並改善低記憶體裝置的使用者體驗。

應用程式壓力測試

應用程式壓力測試 (stressapptest) 是一項記憶體介面測試,可協助您建立真實的高負載情境,以便測試應用程式的各種記憶體和硬體限制。您可以透過定義時間和記憶體限制來編寫檢測設備測試,藉此驗證高記憶體情境的實際體驗。例如,您可以使用下列指令集來推送資料檔案系統中的靜態資料庫,並將其設為可執行,然後執行 20 秒的 990 MB 壓力測試:
    adb push stressapptest /data/local/tmp/
    adb shell chmod 777 /data/local/tmp/stressapptest
    adb shell /data/local/tmp/stressapptest -s 20 -M 990

  

如要進一步瞭解如何安裝工具、常見引數和錯誤處理資訊,請參閱 stressapptest 說明文件。

stressapptest 的觀察結果

stressapptest 等工具可用於要求大於免費提供空間的記憶體配置。這類要求可能會觸發各種快訊,您應從開發方面留意這些快訊。因記憶體可用性低而出發的三個主要快訊包括:
  • SIGABRT:由於要求配置的記憶體大小超過可用記憶體空間,而系統已經處於記憶體壓力之下,因此對您的程序造成嚴重的原生當機。
  • SIGQUIT:產生核心記憶體傾印,並在偵測到檢測設備測試時終止程序。
  • TRIM_MEMORY_EVENTS:這些回呼適用於 Android 4.1 (API 級別 16) 以上版本,並提供程序的詳細記憶體快訊。