引言
前文我們普及了下關(guān)于GC的一些事, 對(duì)GC的一些個(gè)概念, 流程有個(gè)大概的了解. 在Application的啟動(dòng)流程一文中, 我們有提到, Android中每個(gè)App默認(rèn)情況下是運(yùn)行在一個(gè)獨(dú)立進(jìn)程中的, 而這個(gè)獨(dú)立進(jìn)程正是從Zygote孵化出來(lái)的VM進(jìn)程. 也就是說(shuō), 每個(gè)App是運(yùn)行在獨(dú)立的VM空間的. 那么Android是怎么管理這些App的內(nèi)存的呢, 這些獨(dú)立運(yùn)行的VM中的內(nèi)存管理又是怎樣的呢?
今天我們就來(lái)聊下Android中的內(nèi)存管理.
1, Dalvik & ART
Android在4.4之前一直使用的Dalvik虛擬機(jī)作為App的運(yùn)行VM的, 4.4中引入了ART作為開(kāi)發(fā)者備選, 5.0起正式將ART作為默認(rèn)VM了.
我們首先來(lái)簡(jiǎn)單了解下二者:
1.1 Dalvik
如果只是想簡(jiǎn)單了解, 個(gè)人覺(jué)得百度百科上這個(gè)Dalvik的介紹基本就滿足要求了.
如果大家想深入, 可以看下老羅的Android之旅中Dalvik的相關(guān)博文, 從代碼層面上分析了Dalvik的啟動(dòng), 運(yùn)行機(jī)制等. 值得一看.
需要說(shuō)明的是, Dalvik采用的是JIT技術(shù), 在應(yīng)用程序啟動(dòng)時(shí), JIT通過(guò)進(jìn)行連續(xù)的性能分析來(lái)優(yōu)化程序代碼的執(zhí)行, 在程序運(yùn)行的過(guò)程中, Dalvik在不斷的進(jìn)行將字節(jié)碼編譯成機(jī)器碼的工作.
1.2 ART
ART 取自 Android RunTime. Android用其取代Dalvik, 主要目的就是為了提升運(yùn)行性能. 所以, ART相比Dalvik有幾個(gè)關(guān)鍵的提升:
引入AOT(ahead-of-time)預(yù)編譯技術(shù)
在安裝apk的過(guò)程中, ART會(huì)使用dex2oat程序所有的字節(jié)碼預(yù)編譯成了機(jī)器碼. 應(yīng)用程序運(yùn)行過(guò)程中無(wú)需進(jìn)行實(shí)時(shí)的編譯工作, 只需要進(jìn)行直接調(diào)用. 故而提高了應(yīng)用程序的運(yùn)行效率.
GC效率
- 由原來(lái)的兩次GC暫停減少為一次.
- 以較少的GC時(shí)間回收最近分配的, 短命的對(duì)象.
- 提升GC工程學(xué), 使并發(fā)GC更及時(shí).
- 壓縮GC, 以減少后臺(tái)內(nèi)存使用和內(nèi)存碎片.
開(kāi)發(fā)和調(diào)試
- 支持內(nèi)存/方法執(zhí)行的采樣分析.
- 支持更多的調(diào)試技.
- 在Crash report中提供更多信息.
2, Android的內(nèi)存管理方式
ART和Dalvik都是使用paging和memory-mapping(mmapping)來(lái)管理內(nèi)存的. 這就意味著, 任何被分配的內(nèi)存都會(huì)持續(xù)存在, 唯一的釋放這塊內(nèi)存的方式就是釋放對(duì)象引用(讓對(duì)象GC Root不可達(dá)), 故而讓GC程序來(lái)回收內(nèi)存.
2.1 App的內(nèi)存分配和回收
對(duì)于每個(gè)App進(jìn)程來(lái)說(shuō), Heap內(nèi)存被限制在一個(gè)虛擬的內(nèi)存區(qū)間內(nèi). 且定義了邏輯上的使用的Heap Size, 這個(gè)Heap Size在系統(tǒng)限制的最大值之內(nèi)是隨著應(yīng)用的使用情況而變化的.
Heap內(nèi)存的邏輯大小和實(shí)際物理內(nèi)存的大小是不相同的. 后面我們?cè)谑褂肕emory Monitor等內(nèi)存分析工具分析內(nèi)存時(shí), 會(huì)看到一個(gè)叫做Proportional Set Size (PSS)的值, 這個(gè)值才是系統(tǒng)認(rèn)為的你的App所占用的物理內(nèi)存大小.
這個(gè)PSS值也就是實(shí)際物理內(nèi)存大小, 統(tǒng)計(jì)包括了你的應(yīng)用進(jìn)程所占用的內(nèi)存大小, 和共享內(nèi)存中占用的內(nèi)存大小(比例分配方式計(jì)算).
Android VM不會(huì)壓縮Heap內(nèi)存的邏輯大小, 故而無(wú)法通過(guò)碎片整理的方式來(lái)釋放Heap空間, 而只能通過(guò)回收Heap尾部的空內(nèi)存塊來(lái)壓縮邏輯內(nèi)存大小.
這時(shí), 我們的GC就出場(chǎng)了, GC之后, VM會(huì)遍歷Heap找到不被使用的pages, 通過(guò)madvise函數(shù)將其返回給內(nèi)核, 從而釋放這塊被邏輯Heap使用的物理內(nèi)存.
2.2 App內(nèi)存限制
Android是一個(gè)多任務(wù)系統(tǒng), 為了保證多任務(wù)的運(yùn)行, Android給每個(gè)App可使用的Heap大小設(shè)定了一個(gè)限定值.
這個(gè)值是系統(tǒng)設(shè)置的prop值, 系統(tǒng)編譯時(shí)內(nèi)置的, 保存在system/build.prop中. 一般國(guó)內(nèi)的手機(jī)廠商都會(huì)做修改, 根據(jù)手機(jī)配置不同而不同, 可以通過(guò)如下命令查看:
$ adb shell
shell@hwH60:/ $ cat /system/build.prop
以手頭的Huawei 榮耀6為例, heap size相關(guān)的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啟動(dòng)后, 系統(tǒng)分配給它的Heap初始大小. 隨著App使用可增加.
dalvik.vm.heapgrowthlimit
-- 默認(rèn)情況下, App可使用的Heap的最大值, 超過(guò)這個(gè)值就會(huì)產(chǎn)生OOM.
dalvik.vm.heapsize
-- 如果App的manifest文件中配置了largeHeap屬性, 如下.則App可使用的Heap的最大值為此項(xiàng)設(shè)定值.
<application
android:largeHeap="true">
...
</application>
dalvik.vm.heaptargetutilization
-- 當(dāng)前理想的堆內(nèi)存利用率. GC后, Dalvik的Heap內(nèi)存會(huì)進(jìn)行相應(yīng)的調(diào)整, 調(diào)整到當(dāng)前存活的對(duì)象的大小和 / Heap大小 接近這個(gè)選項(xiàng)的值, 即這里的0.75. 注意, 這只是一個(gè)參考值.
dalvik.vm.heapminfree
-- 單次Heap內(nèi)存調(diào)整的最小值.
dalvik.vm.heapmaxfree
-- 單次Heap內(nèi)存調(diào)整的最大值.
也可以直接使用getprop查看單項(xiàng)prop:
$ adb shell getprop dalvik.vm.heapsize
512m
2.3 切換App時(shí)的內(nèi)存管理機(jī)制
Android的進(jìn)程級(jí)別
Android 系統(tǒng)會(huì)盡可能長(zhǎng)時(shí)間地保持應(yīng)用進(jìn)程, 但為了新建進(jìn)程或運(yùn)行更重要的進(jìn)程, 最終需要清除舊進(jìn)程來(lái)回收內(nèi)存. 為了確定保留或終止哪些進(jìn)程, 系統(tǒng)會(huì)根據(jù)進(jìn)程中正在運(yùn)行的組件以及這些組件的狀態(tài), 將每個(gè)進(jìn)程設(shè)定了一個(gè)重要級(jí)別. 必要時(shí), 系統(tǒng)會(huì)首先消除重要性最低的進(jìn)程, 然后是重要性略低的進(jìn)程, 依此類推, 以回收系統(tǒng)資源.
依據(jù)重要程度從大到小依次分為5級(jí):
前臺(tái)進(jìn)程
用戶當(dāng)前操作所必需的進(jìn)程. 如果一個(gè)進(jìn)程滿足以下任一條件, 即視為前臺(tái)進(jìn)程:
- 托管用戶正在交互的 Activity(已調(diào)用 Activity 的 onResume() 方法)
- 托管某個(gè) Service,后者綁定到用戶正在交互的 Activity
- 托管正在“前臺(tái)”運(yùn)行的 Service(服務(wù)已調(diào)用 startForeground())
- 托管正執(zhí)行一個(gè)生命周期回調(diào)的 * Service(onCreate()、onStart() 或 onDestroy())
- 托管正執(zhí)行其 onReceive() 方法的 BroadcastReceiver
只有在內(nèi)在不足以支持它們同時(shí)繼續(xù)運(yùn)行這一萬(wàn)不得已的情況下,系統(tǒng)才會(huì)終止它們。 此時(shí),設(shè)備往往已達(dá)到內(nèi)存分頁(yè)狀態(tài),因此需要終止一些前臺(tái)進(jìn)程來(lái)確保用戶界面正常響應(yīng)。
可見(jiàn)進(jìn)程
沒(méi)有任何前臺(tái)組件、但仍會(huì)影響用戶在屏幕上所見(jiàn)內(nèi)容的進(jìn)程。 如果一個(gè)進(jìn)程滿足以下任一條件,即視為可見(jiàn)進(jìn)程:
- 托管不在前臺(tái)、但仍對(duì)用戶可見(jiàn)的 Activity(已調(diào)用其 onPause() 方法)。例如,如果前臺(tái) Activity 啟動(dòng)了一個(gè)對(duì)話框,允許在其后顯示上一 Activity,則有可能會(huì)發(fā)生這種情況
- 托管綁定到可見(jiàn)(或前臺(tái))Activity 的 Service
可見(jiàn)進(jìn)程被視為是極其重要的進(jìn)程,除非為了維持所有前臺(tái)進(jìn)程同時(shí)運(yùn)行而必須終止,否則系統(tǒng)不會(huì)終止這些進(jìn)程。
服務(wù)進(jìn)程
正在運(yùn)行已使用 startService() 方法啟動(dòng)的服務(wù)且不屬于上述兩個(gè)更高類別進(jìn)程的進(jìn)程。盡管服務(wù)進(jìn)程與用戶所見(jiàn)內(nèi)容沒(méi)有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如,在后臺(tái)播放音樂(lè)或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,除非內(nèi)存不足以維持所有前臺(tái)進(jìn)程和可見(jiàn)進(jìn)程同時(shí)運(yùn)行,否則系統(tǒng)會(huì)讓服務(wù)進(jìn)程保持運(yùn)行狀態(tài)。
后臺(tái)進(jìn)程
包含目前對(duì)用戶不可見(jiàn)的 Activity 的進(jìn)程(已調(diào)用 Activity 的 onStop() 方法)。這些進(jìn)程對(duì)用戶體驗(yàn)沒(méi)有直接影響,系統(tǒng)可能隨時(shí)終止它們,以回收內(nèi)存供前臺(tái)進(jìn)程、可見(jiàn)進(jìn)程或服務(wù)進(jìn)程使用。 通常會(huì)有很多后臺(tái)進(jìn)程在運(yùn)行,因此它們會(huì)保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個(gè)被終止。如果某個(gè) Activity 正確實(shí)現(xiàn)了生命周期方法,并保存了其當(dāng)前狀態(tài),則終止其進(jìn)程不會(huì)對(duì)用戶體驗(yàn)產(chǎn)生明顯影響,因?yàn)楫?dāng)用戶導(dǎo)航回該 Activity 時(shí),Activity 會(huì)恢復(fù)其所有可見(jiàn)狀態(tài)。
空進(jìn)程
不含任何活動(dòng)應(yīng)用組件的進(jìn)程。保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運(yùn)行組件所需的啟動(dòng)時(shí)間。 為使總體系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會(huì)終止這些進(jìn)程。
切換App的內(nèi)存管理
當(dāng)用戶切換App時(shí), 被切換到后臺(tái)的App所使用的內(nèi)存并未因此刪除, 該App進(jìn)程被緩存到一個(gè)LRU緩存中, 以便用戶切換回來(lái)時(shí), 能更快的啟動(dòng)App, 讓多任務(wù)更流暢.
但是, 當(dāng)系統(tǒng)內(nèi)存不夠用的時(shí)候, 就會(huì)根據(jù)LRU特性, 以及上面說(shuō)到的進(jìn)程級(jí)別(同時(shí)也會(huì)考慮該App進(jìn)程占用的內(nèi)存大小)來(lái)決定殺死哪些進(jìn)程, 來(lái)回收內(nèi)存, 以便執(zhí)行當(dāng)前任務(wù).
所以, 如果我們?yōu)榱瞬灰屜到y(tǒng)kill掉我們的App, 可以從進(jìn)程級(jí)別, 內(nèi)存消耗量等幾個(gè)方面進(jìn)行優(yōu)化.
結(jié)語(yǔ)
本文加上GC那些事兒, 我們講了兩篇的理論知識(shí), 相信大家對(duì)Android的內(nèi)存管理有了個(gè)大體的了解, 后面將介紹一些內(nèi)存分析工具以及使用, 結(jié)合實(shí)際來(lái)說(shuō)明怎么分析內(nèi)存問(wèn)題.
參考
轉(zhuǎn)載請(qǐng)注明出處, 歡迎大家分享到朋友圈, 微博~