Android 執行階段 (ART) 和 Dalvik 虛擬機器會利用分頁和記憶體對應 (mmapping) 等方式管理記憶體。這表示任何經過應用程式修改的應用程式 (無論是配置新物件或觸及記憶體對應的頁面),都會繼續留在 RAM 內,無法分頁出去。如果想從應用程式釋放記憶體,唯一的辦法就是釋放應用程式保留的物件參照,藉此讓垃圾收集器取得這些記憶體。但是有一個例外:如果系統想再其他地方使用這些記憶體,那任何經過記憶體對應且並未修改的檔案 (例如程式碼) 都可以分頁到 RAM 外面。
本頁面將說明 Android 如何管理應用程式程序和記憶體配置。如果想進一步瞭解如何更有效地管理應用程式的記憶體,請參閱「管理應用程式的記憶體」。
垃圾收集
在記憶體受到管理的環境裡,例如 ART 或 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) 以上版本,並提供程序的詳細記憶體快訊。