Android彈藥庫——記憶體管理機制與程式模型

刺目啊1199發表於2019-03-04

題外話:喜歡文章的朋友點個贊鼓勵鼓勵唄~

本篇文章是對 Android 記憶體管理、程式管理的簡單總結,主要偏理論性,但是瞭解箇中原理,對 Android 系統的認知、對高質量程式的編寫、對程式的理解都大有裨益~

Android記憶體管理

Android的記憶體管理哲學

Android 是基於 Linux 核心實現的作業系統,而 Linux 的記憶體管理哲學是:Free memory is wasted memory,即記憶體沒得到充分利用就是在浪費資源。

Linux 希望儘可能多的使用記憶體,減少磁碟 IO,因為記憶體的速度比磁碟快得多。Linux 總是在力求快取更多的資料和資訊,記憶體不夠時,將一些不經常使用的資料轉移到交換分割槽(Swap Space)中以釋放更多可用實體記憶體,當然,如果交換分割槽的資料再次被讀寫時,又會被轉移到實體記憶體中,這種設計思路提高了系統的整體效能。

Linux 和 Windows 在記憶體管理機制上的區別
在 Linux 系統使用過程中,你會發現,無論你的電腦記憶體配置多麼優越,仍然不時的發生可用記憶體吃緊的現象,感覺記憶體不夠用了,其實不然。這是 Linux 記憶體管理的優秀特性,無論實體記憶體有多大, Linux 都將其充分利用,將一些程式呼叫過的硬碟資料快取到記憶體,利用記憶體讀寫的高速性提高系統的資料訪問效能。而 Windows 只在需要記憶體時,才為應用分配記憶體,不能充分利用大容量的記憶體空間。換句話說,每增加一些記憶體,Linux 都能將其利用起來,充分發揮硬體投資帶來的好處,而 Windows 只將其作為擺設。

Android 對記憶體的使用方式同樣是“盡最大限度的使用”,這一點繼承了 Linux 的優點。只不過有所不同的是,Linux 側重於儘可能多的快取磁碟資料以降低磁碟 IO 進而提高系統的資料訪問效能,而 Android 側重於儘可能多的快取程式以提高應用啟動和切換速度。Linux 系統在程式活動停止後就結束該程式,而 Android 系統則會在記憶體中儘量長時間的保持應用程式,直到系統需要更多記憶體為止。這些保留在記憶體中的程式,通常情況下不會影響系統整體執行速度,反而會在使用者再次啟用這些程式時,加快程式的啟動速度,因為不用重新載入介面資源了,這是 Android 標榜的特性之一。所以,Android 現在不推薦顯式的“退出”應用。

那為什麼記憶體少的時候執行大型程式會慢呢,原因是:在記憶體剩餘不多時開啟大型程式會觸發系統自身的程式排程策略,這是十分消耗系統資源的操作,特別是在一個程式頻繁向系統申請記憶體的時候。這種情況下系統並不會關閉所有開啟的程式,而是選擇性關閉,頻繁的排程自然會拖慢系統。

一句話概括就是,在佔用大量記憶體情況下,快取提高效能。我們開發者在日常的 app 開發工作中,也大量使用到了快取機制提高應用的效能,提高使用者的體驗。

例如經典的三級快取,先從記憶體中找,記憶體找不到從磁碟上找,磁碟上找不到就只能走網路請求了;

又例如為什麼有的應用要做保活?一個重要的原因,保活能夠使得應用程式快取在 Android 系統中不被清理,從而達到應用 啟動優化(溫馨提示:可點選喲~) 的效果。但要注意的是,一個空的程式也會佔用 10MB 的記憶體,而有些應用啟動就有十幾個程式,甚至有些應用已經從雙程式保活升級到四程式保活,這就過分了,此時應該減少應用啟動的程式數、減少常駐程式、有節操的保活,這對低端機記憶體優化非常重要;

再例如開發工作中常用的 Glide 圖片載入框架、OkHttp 網路請求框架等第三方框架,它們的原始碼設計構架無不是建立在基於優秀快取機制之上(後續的文章,我會對一些常用第三方框架原始碼進行分析)……

Android對應用程式記憶體的分配和回收

Android 為每個程式分配記憶體的時候,採用了彈性分配方式,即剛開始並不會分配很多的記憶體給到每個程式,而是給每一個程式分配一個“夠用”的虛擬記憶體範圍。這個範圍是根據每一個裝置實際的實體記憶體大小來決定的,並且可以隨著應用後續需求而增加,但最多也只能達到系統為每個應用定義的上限。

Android 會在記憶體中儘量長時間的保持應用程式,即使有些程式不再使用了。這樣,當使用者下次啟動應用的時候,只需要恢復當前程式就可以了,不需要重新建立程式,進而達到了減少應用的啟動時間,提升使用者體驗的效果。但是一當 Android 系統發現記憶體不足,而其他為使用者提供更緊急服務的程式又需要記憶體的時候,Android 就會決定關閉某些程式以回收記憶體。這部分就涉及到 Android 的幽靈劊子手 LMK 低記憶體管理機制,low memory killer,它是基於Linux核心的 OOM Killer 機制誕生。

說到這裡,又不得不提一下 oom_score_adj(在 Android 7.0 系統以前是 oom_adj),什麼是 oom_score_adj?它是 Linux 核心分配給每個系統程式的一個值,代表程式的優先順序,程式回收機制就是根據這個優先順序來決定是否進行回收。對於 oom_score_adj 的作用,在這裡,我們只需要瞭解以下幾點即可:

  • 程式的 oom_score_adj 越大,表示此程式優先順序越低,越容易被殺死回收;反之,越小,表示程式優先順序越高,越不容易被殺死回收
  • 普通app程式的 oom_score_adj >= 0,系統程式的 oom_score_adj 才可能 < 0
  • 程式的 oom_score_adj 的值不是一成不變的,Android 系統會根據應用是否在前臺等情況改變該值

疑問   oom_score_adj 的取值範圍是多少,以及oom_score_adj 的每個值代表什麼意思呢?系統會在什麼時候更新 oom_score_adj (即程式優先順序),又會在什麼時候根據 oom_score_adj 的值去選擇性殺程式呢?應用程式怎樣提高 oom_score_adj 的值,從而能夠獲取更高的程式優先順序不被殺死呢?關於這些問題,我們留在下一節“Android程式”中討論。

Android對應用記憶體的限制

為了維護高效的多工環境,Android 為每個應用程式設定了堆大小 DalvikHeapSize 最大限制閾值的硬性限制。 該限制因裝置而異,取決於裝置總體可用的 RAM。 如果應用程式已達到該限制並嘗試分配更多記憶體,就會很容易引發  OutOfMemoryError,即 OOM crash。

提示  在某些情況下,你可能希望查詢系統以準確確定當前裝置上可用的堆空間大小,例如,確定可以安全地保留在快取中的資料量。ActivityManager.getMemoryClass() 可以用來查詢當前應用的HeapSize閾值,這個方法會返回一個整數,表明應用的HeapSize閾值是多少MB,指示應用程式堆可用的兆數。

Android對應用程式的切換

當使用者在應用程式之間切換時,Android 會將非前臺應用程式(即使用者不可見或並沒有執行諸如音樂播放等前臺服務的程式)快取到一個最近最少使用快取(LRU Cache)中。例如,當使用者首次啟動應用程式時,會為其建立一個程式; 但是當使用者離開應用程式時,該程式不會退出。 系統會快取該程式。 如果使用者稍後返回應用程式,系統將重新使用該程式,從而使應用程式切換更快。

如果你的應用程式具有快取程式並且它保留了當前不需要的記憶體,那麼即使使用者未使用它,你的應用程式也會影響系統的整體效能。 當系統記憶體不足時,就會從最近最少使用的程式開始,終止 LRU Cache 中的程式。另外,系統還會綜合考慮保留了最多記憶體的程式,並可能終止它們以釋放 RAM。一句話概括,在程式不再前臺的時候,釋放該釋放的應用快取,因為當系統記憶體不足時,這些未釋放的快取有可能成為“壓倒大象的最後一根稻草”。

當系統開始終止 LRU Cache 中的程式時,它主要是自下而上的。 系統還會考慮哪些程式佔用更多記憶體,因為在它被殺時會為系統提供更多記憶體增益。 因此在整個 LRU 列表中消耗的記憶體越少,保留在列表中並且能夠快速恢復的機會就越大。

Android垃圾回收機制

Android 系統根據所分配物件的型別以及 GC 時系統如何管理這些物件,將程式所使用的記憶體分成了多個空間。新分配的物件位於什麼空間,取決於你的 Android Runtime 是什麼版本,5.0 以上系統採用的是 ART(Android Runtime)虛擬機器,5.0以下采用的是 Dalvik 虛擬機器。

無論是 ART 還是 Dalvik 虛擬機器,都和眾多 Java 虛擬機器一樣,屬於一種託管記憶體環境(程式設計師不需要顯示的管理記憶體的分配與回收,交由系統自動管理)。託管記憶體環境會跟蹤每個記憶體分配, 一旦確定程式不再使用一塊記憶體,它就會將其釋放回堆中,而無需程式設計師的任何干預。回收託管記憶體環境中已經分配但不可達物件記憶體的機制稱為垃圾回收,簡稱 GC 。GC 通過確定物件是否被活動物件引用來確定是否收集該物件,進而動態回收無任何引用的物件佔據的記憶體空間。

Android彈藥庫——記憶體管理機制與程式模型

每個空間都有大小限制,系統會跟蹤整個程式所佔用的記憶體大小。當程式佔用記憶體達到一定程度時,系統就會觸發 GC 回收記憶體,以便將來分配給其它物件:

                    Android彈藥庫——記憶體管理機制與程式模型

ART 虛擬機器相較於 Dalvik 在 GC 的效能上有較大的提升。

  • Dalvik 虛擬機器在 GC 的整個流程中,絕大部分時間會“Stop the world”,即暫停所有執行緒(包括負責渲染介面的主執行緒)的執行,只執行 GC 執行緒,直至 GC 完成,這就會導致一個問題“當 GC 過於頻繁時,阻塞了主執行緒,導致丟幀(Android 系統每 16ms 渲染一幀),最終導致介面的卡頓”:

                 Android彈藥庫——記憶體管理機制與程式模型

  • ART 虛擬機器則在 Dalvik 虛擬機器阻塞性 GC 的基礎上,擴充套件了併發 GC 演算法,消除了大面積的 GC 阻塞時間(當然也不是說全程不阻塞,在某一小段時間內還是會出現“Stop the world”),提升了 GC 的效能,也在一定的程度上優化了卡頓情況:

                         Android彈藥庫——記憶體管理機制與程式模型

Android 的記憶體堆是分代式(Generational)的,意味著它會將所有分配的物件進行分代,然後分代跟蹤這些物件。 例如,最近分配的物件屬於年輕代(Young Generation)。 當一個物件長時間保持活動狀態時,它可以被提升為年老代(Older Generation),之後還能進一步提升為持久代(Permanent Generation)。

                                Android彈藥庫——記憶體管理機制與程式模型

  • Young Generation(年輕代):Faster but more frequent,垃圾回收效率高且頻繁

年輕代分為三個區,一個 eden 區,另外的兩個 S0 和 S1 都是 Survivor 區(S0 和 S1 只是為了說明,兩者實質上一樣,方向可互換)。程式中生成的大部分新的物件都在 eden 區中,當 eden 區滿時,還存活的物件將被複制到其中一個 Survivor 區,當此 Survivor 區的物件佔用空間滿時,此區存活的物件又被複制到另外一個 Survivor 區,當這個 Survivor 區也滿時,從第一個 Survivor 區複製過來的並且此時還存活的物件,將被複制到年老代。

  • Old Generation(年老代):Slower but less frequent,垃圾回收速度慢且相對年輕代頻率較低

年老代存放的是上面年輕代複製過來的物件,也就是在年輕代中還存活的物件,並且區滿了複製過來的。一般來說,年老代中的物件生命週期都比較長。

  • Permanent Generation(持久代)

用於存放靜態的類和方法,垃圾回收對持久代沒有顯著影響。

在三級 Generation 記憶體模型中,每一個區域的大小都是有固定值的,當進入的物件總大小到達某一級記憶體區域閥值的時候就會觸發 GC 機制,進行垃圾回收,騰出空間以便其他物件進入。                                                            

讀到這裡,我們可以總結一下一個物件從被分配開始,在三級記憶體模型中隨時間的遷移的變化流程:

  1. 物件剛建立的時候,存放於 eden 區
  2. 當執行 GC 是,如果物件仍然存活,則複製到 S0 區
  3. 當 S0 區滿時,該區存活物件將複製到 S1 區,然後清空 S0 區,接下來 S0 和 S1 角色互換
  4. 當步驟3達到一定次數(系統版本不同會有差異)後,存活物件將被複制到 Old Generation
  5. 當這個物件在 Old Generation 區域停留累積一定時間後,會被移動到PermanentGeneration區域。PermanentGeneration區域也存放一些靜態檔案,如Java類等。

                             Android彈藥庫——記憶體管理機制與程式模型

現在,我們已經知道了 GC 機制的記憶體分代模型,以及不同代區域的作用,那麼為什麼需要對堆進行分代呢?不分代不能完成他所做的事情麼?最簡單的,為什麼將年輕代以及年老代合併成一個呢,而非要區分開呢?其實不分代完全可以,分代的唯一理由就是優化 GC 效能。我們這麼想想,如果沒有分代,那所有的物件都在同一個區域,當 GC 的時候我們要找到哪些物件需要被回收,這就需要對堆的所有區域進行掃描,這樣子會比較耗時。而我們的大部分物件的生命週期很短暫的,正所謂“朝生夕死”,如果採用分代的策略,我們把新建立的物件放到某一代,當 GC 的時候先把這塊存“朝生夕死”物件的區域進行回收,這樣就會高效率的騰出很大的空間出來。同時,不同代之間可以使用不同的垃圾回收演算法:

  • 年輕代使用 “標記-複製” 法進行 GC
  • 老年代使用 “標記-整理” 法進行 GC

在這裡就不詳細闡述演算法了,想了解 GC 演算法參考以下兩篇文章:
https://juejin.im/post/5c283f06e51d454b98293bda
https://www.cnblogs.com/fangfuhai/p/7203468.html

Android程式

程式生命週期

Android 系統會盡量長時間地幫助我們快取應用的程式,已達到快速啟動的效果,但是當記憶體吃急的時候,系統依舊會將相對不重要的程式從快取中移除,達到記憶體釋放的作用。Android 系統一個基本特徵就是應用程式程式的生命週期並不是由應用本身直接控制的,而是由系統決定的,系統會權衡每個程式對使用者的相對重要程度,以及系統的可用記憶體總量來確定。比如說相對於終止一個正在與使用者互動的 Activity 的程式,系統更可能終止一個在螢幕上不再可見的 Activity 的程式,否則這種後果將會是可怕的,將會給使用者帶來極其不好的使用者體驗。因此,是否終止某個程式取決於該程式中所執行元件的狀態。Android 會有限清理那些已經不再使用的程式,以保證最小的副作用。

作為應用開發者,瞭解各個應用元件(特別是Activity、Service和BroadcastReceiver)如何影響應用程式的生命週期非常重要。不正確的使用這些元件,有可能導致系統在應用執行重要工作時終止程式。

舉個常見的例子, BroadcastReceiver 在其 onReceive() 方法中接收到 Intent 時啟動一個執行緒,然後從該函式返回。而一旦返回,系統就認為該 BroadcastReceiver 不再處於活動狀態,因此也就不再需要其託管程式(除非該程式中還有其他元件處於活動狀態)。這樣一來,系統就有可能隨時終止程式以回收記憶體,而這也最終會導致執行在程式中的執行緒被終止。此問題的解決方案通常是從 BroadcastReceiver 中安排一個 JobService ,以便系統知道在該程式中仍有活動的工作。

為了確定在記憶體不足時終止哪些程式,Android會根據程式中正在執行的元件以及這些元件的狀態,將每個程式放入“重要性層次結構”中。必要時,系統會首先殺死重要性最低的程式,以此類推,以回收系統資源。這就相當於為程式分配了優先順序的概念。

程式優先順序

上面關於 oom_score_adj 程式優先順序留下了幾個疑問,現在,我們一一地解決:

第一個疑問,oom_score_adj 的取值範圍是多少,以及 oom_score_adj 的每個值代表什麼意思呢?

對於每一個執行中的程式,Linux 核心都通過 proc檔案系統 暴露這樣一個檔案來允許其他程式修改指定程式的優先順序:

/proc/[pid]/oom_score_adj。(修改這個檔案需要 root 許可權)

這個檔案允許的值的範圍是:-1000 ~ +1000之間。值越小,表示程式越重要

當記憶體非常緊張時,系統便會遍歷所有程式,以確定哪個程式需要被殺死以回收記憶體,此時便會讀取 oom_score_adj 這個檔案的值。關於這個值的使用,在後面講解程式回收的的時候,我們會詳細講解。

提示:在 Linux 2.6.36 之前的版本中,Linux 提供調整優先順序的檔案是/proc/[pid]/oom_adj。這個檔案允許的值的範圍是-17 ~ +15之間。數值越小表示程式越重要。 這個檔案在新版的 Linux 中已經廢棄。

但你仍然可以使用這個檔案,當你修改這個檔案的時候,核心會直接進行換算,將結果反映到 oom_score_adj 這個檔案上。

換算公式:oom_score_adj = oom_adj * 1000 / 17

Android 系統是基於 Linux 系統的,因此在早期的 Android 版本實現中也是依賴 oom_adj 這個檔案。但是在 Android 7.0 版本開始,已經切換到使用 oom_score_adj 這個檔案,優先順序的取值由原本的-17~+15變為了-1000~+1000,這樣的調整可以更進一步地細化程式的優先順序,比如在 VISIBLE_APP_ADJ(100)PERCEPTIBLE_APP_ADJ(200) 之間,可以有adj =101、102級別的程式。

為了便於管理,ProcessList.java 中預定義了 oom_score_adj 的可能取值。

其實這裡的預定義值也是對應用程式的一種分類,它們是:

ADJ級別 取值 含義
NATIVE_ADJ -1000 native程式
SYSTEM_ADJ -900 僅指system_server程式
PERSISTENT_PROC_ADJ -800 系統persistent程式
PERSISTENT_SERVICE_ADJ -700 關聯著系統或persistent程式
FOREGROUND_APP_ADJ 0 前臺程式
VISIBLE_APP_ADJ 100 可見程式
PERCEPTIBLE_APP_ADJ 200 可感知程式,比如後臺音樂播放
BACKUP_APP_ADJ 300 備份程式
HEAVY_WEIGHT_APP_ADJ 400 重量級程式
SERVICE_ADJ 500 服務程式
HOME_APP_ADJ 600 Home程式
PREVIOUS_APP_ADJ 700 上一個程式
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可見程式的adj最小值
CACHED_APP_MAX_ADJ 906 不可見程式的adj最大值


提示
   同一個程式,在不同狀態下,其 oom_score_adj 值是不一樣的,也就是說程式的優先順序不是一成不變的。從表中,我們可以清晰地看出每個 oom_score_adj 值的含義,以及程式應具備什麼樣的特徵才能夠獲取相對應的優先順序。

由上面的表,我們可以看出,FOREGROUND_APP_ADJ = 0,這個是前臺應用程式的優先順序,這是使用者正在互動的應用,它們是很重要的,系統不應當把它們回收了,這個也是普通應用程式的最高優先順序了,小於0的優先順序都是由系統程式持有。

第二個疑問,系統會在什麼時候更新 oom_score_adj(即程式優先順序),又會在什麼時候根據 oom_score_adj 的值去選擇性殺程式呢(關於這個疑問,在這裡只紙上得來終覺淺,要想深入瞭解,還是要進到 Android 原始碼的世界中仔細研讀~)?

系統什麼時候更新 oom_score_adj ?

系統會對處於不同狀態的程式設定不同的優先順序。但實際上,程式的狀態是一直在變化中的。例如:使用者可以隨時會啟動一個新的 Activity,或者將一個前臺的 Activity 切換到後臺。在這個時候,發生狀態變化的 Activity 的所在程式的優先順序就需要進行更新。

Android彈藥庫——記憶體管理機制與程式模型

並且,Activity 可能會使用其他的 Service 或者 ContentProvider。當 Activity 的程式優先順序發生變化的時候,它所使用的 Service 或者 ContentProvider 的優先順序也應當發生變化。

ActivityManagerService 中有如下兩個方法用來更新程式的優先順序:

  • final boolean updateOomAdjLocked(ProcessRecord app)
  • final void updateOomAdjLocked()

第一個方法是針對指定的單個程式更新優先順序。第二個是對所有程式更新優先順序。

在下面的這些情況下,需要對指定的應用程式更新優先順序:

  • 當有一個新的程式開始使用本程式中的 ContentProvider
  • 當本程式中的一個 Service 被其他程式 bind 或者 unbind
  • 當本程式中的 Service 的執行完成或者退出了
  • 當本程式中一個 BroadcastReceiver 正在接受廣播
  • 當本程式中的 BackUpAgent 啟動或者退出了

在有些情況下,系統需要對所有應用程式的優先順序進行更新,譬如:

  • 當有一個新的程式啟動時
  • 當有一個程式退出時
  • 當系統在清理後臺程式時
  • 當有一個程式被標記為前臺程式時
  • 當有一個程式進入或者退出cached狀態時
  • 當系統鎖屏或者解鎖時
  • 當有一個 Activity 啟動或者退出時
  • 當系統正在處理一個廣播事件時
  • 當前臺 Activity 發生改變時
  • 當有一個 Service 啟動時

系統什麼時候根據 oom_score_adj 殺程式?

在 ActivityManagerService 呼叫updateOomAdjLocked()時,會判斷程式是否需要被殺死,若是,則呼叫ProceeRecord::kill()方法殺死該程式:

Android彈藥庫——記憶體管理機制與程式模型

預設限制 empty 或 cached 程式的上限為 16 個,並且 empty 超過 8 個時會清理掉 30 分鐘沒有活躍的程式。 cached 和 empty 主要是區別是否有 Activity。


第三個疑問,應用程式怎樣降低 oom_score_adj 值,從而能夠獲取更高的程式優先順序不被殺死呢?

在 Android 系統中,通常按照優先順序將程式分為五個等級(優先順序從高往低排),LMK(Low Memory Killer) 殺死程式時,將遵從 空程式 -> 後臺程式 -> 服務程式 -> 可見程式 -> 前臺程式 的順序,也就是說,要想降低 oom_score_adj 值,降低程式被 LMK 殺死的機率,我們可以控制程式在服務程式 -> 可見程式 -> 前臺程式上,越接近前臺程式的級別,越不會被殺死:

                 Android彈藥庫——記憶體管理機制與程式模型

  • 前臺程式(foreground process,正常不會被殺死)

前臺程式是使用者當前操作所必需的程式。如果一個程式滿足以下任一條件,即視為前臺程式:

  1. 託管使用者正在互動的 Activity(已呼叫 Activity 的 onResume() 方法)

  2. 託管某個 Service,後者繫結到使用者正在互動的 Activity

  3. 託管正執行一個生命週期回撥的 Service(onCreate()、onStart() 或 onDestroy())

  4. 託管正執行其 onReceive() 方法的 BroadcastReceiver

通常,系統中只會有少量幾個前臺程式的存在,前臺程式是最高階的程式,只有在系統記憶體極其不足,甚至系統剩餘記憶體都不足以讓這些前臺程式正常執行的情況下,系統才會殺死他們,回收記憶體空間。在這種情況下,裝置往往已達到記憶體分頁狀態,因此需要終止一些前臺程式來確保使用者介面正常響應。

  • 可見程式(visible process,正常不會被殺死

可見程式是沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程式,殺死這類程式也會明顯影響使用者體驗。 如果一個程式滿足以下任一條件,即視為可見程式:

  1. 託管不在前臺、但仍對使用者可見的 Activity(已呼叫其 onPause() 方法)。例如,啟動了一個對話方塊樣式的前臺 activity ,此時在其後面仍然可以看到前一個Activity。(執行時許可權對話方塊就屬於此類。考慮一下,還有哪種情況會導致只觸發onPause而不觸發onStop?)
  2. 託管通過 Service.startForeground() 啟動的前臺Service。(Service.startForeground():它要求系統將它視為使用者可察覺到的服務,或者基本上對使用者是可見的。)
  3. 託管系統用於某個使用者可察覺的特定功能的Service,比如動態桌布、輸入法服務等等。

可見程式被視為是極其重要的程式,除非為了維持所有前臺程式同時執行而必須終止,否則系統不會終止這些程式。如果這類程式被殺死,從使用者的角度看,這意味著當前 activity 背後的可見 activity 會被黑屏代替。

  • 服務程式(service process,正常不會被殺死

正在執行已使用 startService() 方法啟動的服務且不屬於上述兩個更高類別程式的程式。儘管服務程式與使用者所見內容沒有直接關聯,但是它們通常在執行一些使用者關心的操作(例如,後臺網路上傳或下載資料)。因此,除非記憶體不足以維持所有前臺程式和可見程式同時執行,否則系統會讓服務程式保持執行狀態。

  • 後臺程式(Background / Cached Process,隨時被殺死

這類程式一般會持有一個或多個目前對使用者不可見的 Activity (已呼叫 Activity 的 onStop() 方法)。它們不是當前所必須的,因此當其他更高優先順序的程式需要記憶體時,系統可能隨時終止它們以回收記憶體。但如果正確實現了Activity的生命週期,即便系統終止了程式,當使用者再次返回應用時也不會影響使用者體驗:關聯Activity在新的程式中被重新建立時可以恢復之前儲存的狀態。

  • 空程式(Empty Process,隨時被殺死

不含任何活動應用元件的程式。保留這種程式的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程式快取和底層核心快取之間保持平衡,系統往往會終止這些程式。

總結

到此,我們對 Android 的記憶體管理機制,以及程式模型都進行了概述性的分析,也許有的讀者會覺得“Android 應用開發根本用不上這些東西,學來有什麼用”,但是真的是這樣嗎?

至少我不這麼認為,瞭解了這些系統執行機制,我們會對 Android 系統有更深的理解,對系統的記憶體機制,程式機制有一定的認知,對我們書寫高質量程式碼,日常攻克難題,整體架構思想以及效能優化工作都有很大的幫助。

在這篇文章,我們學到了什麼?最主要的有以下幾點:

  • Android 記憶體管理的哲學——Free memory is wasted memory,很大程度上,我們日常開發工作的的快取機制,程式保活機制(有節操的保活~)就是對這句話的很好縮影
  • 堆分代模型、GC 垃圾回收機制概述
  • 程式的優先順序:空程式 -> 後臺程式 -> 服務程式 -> 可見程式 -> 前臺程式,從左到右,優先順序越來越高,系統傾向於殺死低優先順序的程式,所以,程式的保活可以從提高程式優先順序做起
  • Android 系統程式管理的簡單概述

最後,本人水平有限,不免會有錯誤,如有錯誤,請多多指教~~~

題外話:喜歡文章的朋友點個贊鼓勵鼓勵唄~

參考資料:
羅彧成老師書籍《Android應用效能優化最佳時間》
https://developer.android.google.cn/guide/components/processes-and-threads#Processes
https://developer.android.google.cn/guide/components/activities/process-lifecycle
http://gityuan.com/2015/10/01/process-lifecycle/
https://juejin.im/post/5c283f06e51d454b98293bda
https://paul.pub/android-process-priority/
https://gityuan.com/2018/05/19/android-process-adj/

相關文章