Android 是如何管理 App 記憶體的 — Android 記憶體優化第二彈

anly_jun發表於2019-03-01

引言

前文我們普及了下關於GC的一些事, 對GC的一些個概念, 流程有個大概的瞭解. 在Application的啟動流程一文中, 我們有提到, Android中每個App預設情況下是執行在一個獨立程式中的, 而這個獨立程式正是從Zygote孵化出來的VM程式. 也就是說, 每個App是執行在獨立的VM空間的. 那麼Android是怎麼管理這些App的記憶體的呢, 這些獨立執行的VM中的記憶體管理又是怎樣的呢?

今天我們就來聊下Android中的記憶體管理.

1, Dalvik & ART

Android在4.4之前一直使用的Dalvik虛擬機器作為App的執行VM的, 4.4中引入了ART作為開發者備選, 5.0起正式將ART作為預設VM了.

我們首先來簡單瞭解下二者:

1.1 Dalvik

如果只是想簡單瞭解, 個人覺得百度百科上這個Dalvik的介紹基本就滿足要求了.

如果大家想深入, 可以看下老羅的Android之旅Dalvik的相關博文, 從程式碼層面上分析了Dalvik的啟動, 執行機制等. 值得一看.

需要說明的是, Dalvik採用的是JIT技術, 在應用程式啟動時, JIT通過進行連續的效能分析來優化程式程式碼的執行, 在程式執行的過程中, Dalvik在不斷的進行將位元組碼編譯成機器碼的工作.

1.2 ART

ART 取自 Android RunTime. Android用其取代Dalvik, 主要目的就是為了提升執行效能. 所以, ART相比Dalvik有幾個關鍵的提升:

引入AOT(ahead-of-time)預編譯技術

在安裝apk的過程中, ART會使用dex2oat程式所有的位元組碼預編譯成了機器碼. 應用程式執行過程中無需進行實時的編譯工作, 只需要進行直接呼叫. 故而提高了應用程式的執行效率.

GC效率

  • 由原來的兩次GC暫停減少為一次.
  • 以較少的GC時間回收最近分配的, 短命的物件.
  • 提升GC工程學, 使併發GC更及時.
  • 壓縮GC, 以減少後臺記憶體使用和記憶體碎片.

開發和除錯

  • 支援記憶體/方法執行的取樣分析.
  • 支援更多的除錯技.
  • 在Crash report中提供更多資訊.

2, Android的記憶體管理方式

ART和Dalvik都是使用pagingmemory-mapping(mmapping)來管理記憶體的. 這就意味著, 任何被分配的記憶體都會持續存在, 唯一的釋放這塊記憶體的方式就是釋放物件引用(讓物件GC Root不可達), 故而讓GC程式來回收記憶體.

2.1 App的記憶體分配和回收

對於每個App程式來說, Heap記憶體被限制在一個虛擬的記憶體區間內. 且定義了邏輯上的使用的Heap Size, 這個Heap Size在系統限制的最大值之內是隨著應用的使用情況而變化的.

Heap記憶體的邏輯大小和實際實體記憶體的大小是不相同的. 後面我們在使用Memory Monitor等記憶體分析工具分析記憶體時, 會看到一個叫做Proportional Set Size (PSS)的值, 這個值才是系統認為的你的App所佔用的實體記憶體大小.

這個PSS值也就是實際實體記憶體大小, 統計包括了你的應用程式所佔用的記憶體大小, 和共享記憶體中佔用的記憶體大小(比例分配方式計算).

Android VM不會壓縮Heap記憶體的邏輯大小, 故而無法通過碎片整理的方式來釋放Heap空間, 而只能通過回收Heap尾部的空記憶體塊來壓縮邏輯記憶體大小.

這時, 我們的GC就出場了, GC之後, VM會遍歷Heap找到不被使用的pages, 通過madvise函式將其返回給核心, 從而釋放這塊被邏輯Heap使用的實體記憶體.

2.2 App記憶體限制

Android是一個多工系統, 為了保證多工的執行, Android給每個App可使用的Heap大小設定了一個限定值.

這個值是系統設定的prop值, 系統編譯時內建的, 儲存在system/build.prop中. 一般國內的手機廠商都會做修改, 根據手機配置不同而不同, 可以通過如下命令檢視:

$ adb shell
shell@hwH60:/ $ cat /system/build.prop複製程式碼

以手頭的Huawei 榮耀6為例, heap size相關的prop如下:

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=2m
dalvik.vm.heapmaxfree=8m複製程式碼

其中:

dalvik.vm.heapstartsize

— App啟動後, 系統分配給它的Heap初始大小. 隨著App使用可增加.

dalvik.vm.heapgrowthlimit

— 預設情況下, App可使用的Heap的最大值, 超過這個值就會產生OOM.

dalvik.vm.heapsize

— 如果App的manifest檔案中配置了largeHeap屬性, 如下.則App可使用的Heap的最大值為此項設定值.

<application
    android:largeHeap="true">
    ...
</application>複製程式碼

dalvik.vm.heaptargetutilization

— 當前理想的堆記憶體利用率. GC後, Dalvik的Heap記憶體會進行相應的調整, 調整到當前存活的物件的大小和 / Heap大小 接近這個選項的值, 即這裡的0.75. 注意, 這只是一個參考值.

dalvik.vm.heapminfree

— 單次Heap記憶體調整的最小值.

dalvik.vm.heapmaxfree

— 單次Heap記憶體調整的最大值.

也可以直接使用getprop檢視單項prop:

$ adb shell getprop dalvik.vm.heapsize
512m複製程式碼

2.3 切換App時的記憶體管理機制

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 會恢復其所有可見狀態。

空程式

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

切換App的記憶體管理

當使用者切換App時, 被切換到後臺的App所使用的記憶體並未因此刪除, 該App程式被快取到一個LRU快取中, 以便使用者切換回來時, 能更快的啟動App, 讓多工更流暢.

但是, 當系統記憶體不夠用的時候, 就會根據LRU特性, 以及上面說到的程式級別(同時也會考慮該App程式佔用的記憶體大小)來決定殺死哪些程式, 來回收記憶體, 以便執行當前任務.

所以, 如果我們為了不要讓系統kill掉我們的App, 可以從程式級別, 記憶體消耗量等幾個方面進行優化.

結語

本文加上GC那些事兒, 我們講了兩篇的理論知識, 相信大家對Android的記憶體管理有了個大體的瞭解, 後面將介紹一些記憶體分析工具以及使用, 結合實際來說明怎麼分析記憶體問題.

參考


轉載請註明出處, 歡迎大家分享到朋友圈, 微博~

相關文章