Android是如何管理App內(nèi)存的--Android內(nèi)存優(yōu)化第二彈

cover

引言

前文我們普及了下關(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都是使用pagingmemory-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)注明出處, 歡迎大家分享到朋友圈, 微博~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,730評(píng)論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,118評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,266評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,482評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,684評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,953評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,751評(píng)論 3 394
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容