天天看點

Android記憶體優化—Android的記憶體管理方式

記憶體管理機制

從作業系統的角度來說,記憶體就是一塊資料存儲區域,屬于可被作業系統排程的資源。現代多任務(程序)的作業系統中,記憶體管理尤為重要,作業系統需要為每一個程序合理的配置設定記憶體資源,是以可以從兩方面來了解作業系統的記憶體管理機制。

第一:配置設定機制。為每一個程序配置設定一個合理的記憶體大小,保證每一個程序能夠正常的運作,不至于記憶體不夠使用或者每個程序占用太多的記憶體。

第二:回收機制。在系統記憶體不足打的時候,需要有一個合理的回收再配置設定的機制,以保證新的程序可以正常運作。回收的時候就要殺死那些正在占有記憶體的程序,作業系統需要提供一個合理的殺死這些程序的機制,以保證更少的副作用。

Android系統也遵從上述的機制,官網對Android記憶體管理的描述如下:

Android并沒有為記憶體提供交換區(Swap space),但是它有使用paging與memory-mapping(mmapping)的機制來管理記憶體。這意味着任何你修改的記憶體(無論是通過配置設定新的對象還是去通路mmaped pages中的内容)都會貯存在RAM中,而且不能被paged out。是以唯一完整釋放記憶體的方法是釋放那些你可能hold住的對象的引用,當這個對象沒有被任何其他對象所引用的時候,它就能夠被GC回收了。隻有一種例外是:如果系統想要在其他地方重用這個對象。(整體也分成兩部分:配置設定和回收)

​​Android官網對Android記憶體管理的概述​​

分頁(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記憶體優化—Android的記憶體管理方式

綜上所述,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記憶體優化—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文檔。

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

回收收益