Android記憶體優化—Android的記憶體管理方式
記憶體管理機制
從作業系統的角度來說,記憶體就是一塊資料儲存區域,屬於可被作業系統排程的資源。現代多工(程序)的作業系統中,記憶體管理尤為重要,作業系統需要為每一個程序合理的分配記憶體資源,所以可以從兩方面來理解作業系統的記憶體管理機制。
第一:分配機制。為每一個程序分配一個合理的記憶體大小,保證每一個程序能夠正常的執行,不至於記憶體不夠使用或者每個程序佔用太多的記憶體。
第二:回收機制。在系統記憶體不足打的時候,需要有一個合理的回收再分配的機制,以保證新的程序可以正常執行。回收的時候就要殺死那些正在佔有記憶體的程序,作業系統需要提供一個合理的殺死這些程序的機制,以保證更少的副作用。
Android系統也遵從上述的機制,官網對Android記憶體管理的描述如下:
Android並沒有為記憶體提供交換區(Swap space),但是它有使用paging與memory-mapping(mmapping)的機制來管理記憶體。這意味著任何你修改的記憶體(無論是通過分配新的物件還是去訪問mmaped pages中的內容)都會貯存在RAM中,而且不能被paged out。因此唯一完整釋放記憶體的方法是釋放那些你可能hold住的物件的引用,當這個物件沒有被任何其他物件所引用的時候,它就能夠被GC回收了。只有一種例外是:如果系統想要在其他地方重用這個物件。(整體也分成兩部分:分配和回收)
分頁(Paging) 是一種作業系統裡記憶體管理的一種技術,可以使電腦的主存可以使用儲存在輔助記憶體中的資料。作業系統會將輔助記憶體(通常是磁碟)中的資料分割槽成固定大小的區塊,稱為“頁”(pages)。當不需要時,將分頁由主存(通常是記憶體)移到輔助記憶體;當需要時,再將資料取回,載入主存中。分頁/虛擬記憶體能有助“大大地”降低整體及額外非必要的 I/O 次數,提高系統整體運作效能。分頁是虛擬記憶體技術中的重要部分。
記憶體對映(Memory-mapped file) 是一段虛記憶體逐位元組對應於一個檔案或類檔案的資源,使得應用程式處理對映部分如同訪問主記憶體。
記憶體分配機制
大家先想一個問題,假設有一個記憶體為1G的Android裝置,上面運行了一個非常非常吃記憶體的應用,如果沒有任何機制的情況下是不是用著用著整個裝置會因為我們這個應用把1G記憶體吃光然後整個系統執行癱瘓呢?
當然,Google的工程師才不會這麼傻的把系統設計這麼差勁。為了使系統不存在我們上面假想情況且能安全快速的執行,Android的框架使得每個應用程式都執行在單獨的程序中。
Android應用的程序都是從一個叫做Zygote的程序fork出來的。Zygote程序在系統啟動,並載入通用的framework的程式碼與資源之後開始啟動。為了啟動一個新的程式程序,系統會fork Zygote程序生成一個新的程序,然後在新的程序中載入並執行應用程式的程式碼。framework的程式碼和資源在應用的所有程序之間進行共享。
如果應用在執行時再存在上面假想的情況,那麼癱瘓的只會是自己的程序,不會直接影響系統執行及其他程序執行。
每個應用程序都對應自己唯一的虛擬機器例項。
既然每個Android應用程式都執行在自己的虛擬機器中,那瞭解Java的一定明白,每個虛擬機器必定會有堆記憶體閾值限制(值得一提的是這個閾值一般都由廠商依據硬體配置及裝置特性自己設定,沒有統一標準,可以為64M,也可以為128M等),也即一個應用程序同時存在的物件必須小於閾值規定的記憶體大小才可以正常執行。
如果你的應用佔用記憶體空間已經接近這個閾值,此時再嘗試分配記憶體的話,很容易引發OutOfMemoryError錯誤。
Android對記憶體的使用方式是“盡最大限度的使用”,這一點繼承了Linux的優點。但也有些不同,Android會在記憶體中儲存儘可能多的資料,即使有些程序不再使用了,但是它的資料還被儲存在記憶體中,所以Android現在不推薦顯式的“退出”應用。因為這樣,當用戶下次再啟動應用的時候,只需要恢復當前程序就可以了,不需要重新建立程序,這樣就可以減少應用的啟動時間。
Android系統並不會對Heap中空閒記憶體區域做碎片整理。系統僅僅會在新的記憶體分配之前判斷Heap的尾端剩餘空間是否足夠,如果空間不夠會觸發GC操作,從而騰出更多空閒的記憶體空間。在Android的高階系統版本里面針對Heap空間有一個Generational Heap Memory的模型,最近分配的物件會存放在Young Generation區域。當這個物件在該區域停留的時間達到一定程度,它會被移動到Old Generation,最後累積一定時間再移動到Permanent Generation區域。系統會根據記憶體中不同的記憶體資料型別分別執行不同的GC操作。例如,剛分配到Young Generation區域的物件通常更容易被銷燬回收,同時在Young Generation區域的GC操作速度會比Old Generation區域的GC操作速度更快,如下圖:
綜上所述,Android記憶體的分配機制要點如下:
1、每個應用程式都執行在單獨的程序中
2、應用程式的程序從Zygote程序fork出來
3、每個應用程序都對應自己唯一的虛擬機器例項
4、每個虛擬機器都有堆記憶體閾值限制
5、即使程序退出了,資料仍然在記憶體中
6、物件根據建立的時間放置在young、old、permanent三個區
記憶體回收機制
在Android系統中,每一個Generation的記憶體區域都有固定的大小。隨著新的物件陸續被分配到此區域,當物件總的大小臨近這一級別記憶體區域的閥值時,會觸發GC操作,以便騰出空間來存放其他新的物件
通常情況下,GC發生的時候,所有的執行緒都是會被暫停的。執行GC所佔用的時間和它發生在哪一個Generation也有關係,Young Generation中的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長。執行時間的長短也和當前Generation中的物件數量有關,遍歷樹結構查詢20000個物件比起遍歷50個物件自然是要慢很多的。
GC操作由Dalvik虛擬機器執行。
Android系統記憶體回收示意圖如下:
上面說的是一個程序內的記憶體回收,如果整個系統的記憶體快滿了該如何回收呢?
我們知道,Android對記憶體的使用方式是“盡最大限度的使用”,這一點繼承了Linux的優點。Android會在記憶體中儲存儘可能多的資料,即使有些程序不再使用了,但是它的資料還被儲存在記憶體中,所以Android現在不推薦顯式的“退出”應用。因為這樣,當用戶下次再啟動應用的時候,只需要恢復當前程序就可以了,不需要重新建立程序,這樣就可以減少應用的啟動時間。只有當Android系統發現記憶體不夠使用,需要回收記憶體的時候,Android系統就會需要殺死其他程序,來回收足夠的記憶體。但是Android也不是隨便殺死一個程序,比如說一個正在與使用者互動的程序,這種後果是可怕的。所以Android會有限清理那些已經不再使用的程序,以保證最小的副作用。
Android殺死程序有兩個參考條件:程序優先順序和回收收益
程序優先順序
Android 系統將盡量長時間地保持應用程序,但為了新建程序或執行更重要的程序,最終需要移除舊程序來回收記憶體。 為了確定保留或終止哪些程序,系統會根據程序中正在執行的元件以及這些元件的狀態,將每個程序放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的程序,然後是重要性略遜的程序,依此類推,以回收系統資源。
重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類程序(第一個程序最重要,將是最後一個被終止的程序):
前臺程序是使用者當前操作所必需的程序,如果一個程序滿足以下任一條件,即視為前臺程序:
託管使用者正在互動的 Activity(已呼叫 Activity 的 onResume() 方法)
託管某個 Service,後者繫結到使用者正在互動的 Activity
託管正在“前臺”執行的 Service(服務已呼叫 startForeground())
託管正執行一個生命週期回撥的 Service(onCreate()、onStart() 或 onDestroy())
託管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前臺程序都為數不多。只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們。 此時,裝置往往已達到記憶體分頁狀態,因此需要終止一些前臺程序來確保使用者介面正常響應。
可見程序指沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程序。 如果一個程序滿足以下任一條件,即視為可見程序:
託管不在前臺、但仍對使用者可見的 Activity(已呼叫其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話方塊,允許在其後顯示上一 Activity,則有可能會發生這種情況。
託管繫結到可見(或前臺)Activity 的 Service。
可見程序被視為是極其重要的程序,除非為了維持所有前臺程序同時執行而必須終止,否則系統不會終止這些程序。
服務程序指正在執行,已使用 startService() 方法啟動的服務且不屬於上述兩個更高類別程序的程序。儘管服務程序與使用者所見內容沒有直接關聯,但是它們通常在執行一些使用者關心的操作(例如,在後臺播放音樂或從網路下載資料)。因此,除非記憶體不足以維持所有前臺程序和可見程序同時執行,否則系統會讓服務程序保持執行狀態。
後臺程序指包含目前對使用者不可見的 Activity 的程序(已呼叫 Activity 的 onStop() 方法)。這些程序對使用者體驗沒有直接影響,系統可能隨時終止它們,以回收記憶體供前臺程序、可見程序或服務程序使用。 通常會有很多後臺程序在執行,因此它們會儲存在 LRU (最近最少使用)列表中,以確保包含使用者最近檢視的 Activity 的程序最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並儲存了其當前狀態,則終止其程序不會對使用者體驗產生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。 有關儲存和恢復狀態的資訊,請參閱 Activity文件。
空程序不含任何活動應用元件的程序。保留這種程序的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程序快取和底層核心快取之間保持平衡,系統往往會終止這些程序。
根據程序中當前活動元件的重要程度,Android 會將程序評定為它可能達到的最高級別。例如,如果某程序託管著服務和可見 Activity,則會將此程序評定為可見程序,而不是服務程序。
回收收益
當Android系統開始殺死LRU快取中的程序時,系統會判斷每個程序殺死後帶來的回收收益。因為Android總是傾向於殺死一個能回收更多記憶體的程序,從而可以殺死更少的程序,來獲取更多的記憶體。殺死的程序越少,對使用者體驗的影響就越小。