10月14日-16日,由CSDN和創(chuàng)新工場聯(lián)合主辦的MDCC 2015中國移動開發(fā)者大會將在北京新云南皇冠假日酒店隆重召開,現(xiàn)在搶注大會門票,即享多重好禮!在平臺與技術(shù)iOS專場議題全方位揭秘之后,平臺與技術(shù)Android專場也有新動作!與會講師——騰訊Android應(yīng)用開發(fā)工程師 胡凱圍繞著“Android內(nèi)存優(yōu)化之OOM”進行了非常深度的技術(shù)分享。
以下為正文:
Android的內(nèi)存優(yōu)化是性能優(yōu)化中很重要的一部分,而避免OOM又是內(nèi)存優(yōu)化中比較核心的一點。這是一篇關(guān)于內(nèi)存優(yōu)化中如何避免OOM的總結(jié)性概要文章,內(nèi)容大多都是和OOM有關(guān)的實踐總結(jié)概要。理解錯誤或是偏差的地方,還請多包涵指正,謝謝!
(一)Android的內(nèi)存管理機制
Google在Android的官網(wǎng)上有這樣一篇文章,初步介紹了Android是如何管理應(yīng)用的進程與內(nèi)存分配: http://developer.android.com/training/articles/memory.html。 Android系統(tǒng)的Dalvik虛擬機扮演了常規(guī)的內(nèi)存垃圾自動回收的角色,Android系統(tǒng)沒有為內(nèi)存提供交換區(qū),它使用 paging與 memory-mapping(mmapping)的機制來管理內(nèi)存,下面簡要概述一些Android系統(tǒng)中重要的內(nèi)存管理基礎(chǔ)概念。
1)共享內(nèi)存
Android系統(tǒng)通過下面幾種方式來實現(xiàn)共享內(nèi)存:
- Android應(yīng)用的進程都是從一個叫做Zygote的進程fork出來的。Zygote進程在系統(tǒng)啟動,并載入通用的framework的代碼與資源之后開始啟動。為了啟動一個新的程序進程,系統(tǒng)會fork Zygote進程生成一個新的進程,然后在新的進程中加載并運行應(yīng)用程序的代碼。這就使得大多數(shù)的RAM pages被用來分配給framework的代碼,同時促使RAM資源能夠在應(yīng)用的所有進程之間進行共享。
- 大多數(shù)static的數(shù)據(jù)被mmapped到一個進程中。這不僅僅讓同樣的數(shù)據(jù)能夠在進程間進行共享,而且使得它能夠在需要的時候被paged out。常見的static數(shù)據(jù)包括Dalvik Code、app resources、so文件等。
- 大多數(shù)情況下,Android通過顯式的分配共享內(nèi)存區(qū)域(例如ashmem或gralloc)來實現(xiàn)動態(tài)RAM區(qū)域能夠在不同進程之間進行共享的機制。比如,Window Surface在App與Screen Compositor之間使用共享的內(nèi)存,Cursor Buffers在Content Provider與Clients之間共享內(nèi)存。
2)分配與回收內(nèi)存
- 每一個進程的Dalvik Heap都反映了使用內(nèi)存的占用范圍。這就是通常邏輯意義上提到的Dalvik Heap Size,它可以隨著需要進行增長,但是增長行為會有一個系統(tǒng)為它設(shè)定上限。
- 邏輯上講的Heap Size和實際物理意義上使用的內(nèi)存大小是不對等的,Proportional Set Size(PSS)記錄了應(yīng)用程序自身占用以及與其他進程進行共享的內(nèi)存。
- Android系統(tǒng)并不會對Heap中空閑內(nèi)存區(qū)域做碎片整理。系統(tǒng)僅僅會在新的內(nèi)存分配之前判斷Heap的尾端剩余空間是否足夠,如果空間不夠會觸發(fā)GC操作,從而騰出更多空閑的內(nèi)存空間。在Android的高級系統(tǒng)版本里面針對Heap空間有一個Generational Heap Memory的模型,最近分配的對象會存放在Young Generation區(qū)域。當(dāng)這個對象在該區(qū)域停留的時間達到一定程度,它會被移動到Old Generation,最后累積一定時間再移動到Permanent Generation區(qū)域。系統(tǒng)會根據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的GC操作。例如,剛分配到Y(jié)oung Generation區(qū)域的對象通常更容易被銷毀回收,同時在Young Generation區(qū)域的GC操作速度會比Old Generation區(qū)域的GC操作速度更快(如圖1所示)。
圖1 根據(jù)不同內(nèi)存數(shù)據(jù)類型執(zhí)行不同GC操作
每一個Generation的內(nèi)存區(qū)域都有固定的大小。隨著新的對象陸續(xù)被分配到此區(qū)域,當(dāng)對象總的大小臨近這一級別內(nèi)存區(qū)域的閥值時,會觸發(fā)GC操作,以便騰出空間來存放其他新的對象(如圖2所示)。
圖2 對象值臨近閥值觸發(fā)GC操作
通常情況下,GC發(fā)生的時候,所有的線程都是會被暫停的。執(zhí)行GC所占用的時間和它發(fā)生在哪一個Generation也有關(guān)系,Young Generation中的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長。執(zhí)行時間的長短也和當(dāng)前Generation中的對象數(shù)量有關(guān),遍歷樹結(jié)構(gòu)查找20000個對象比起遍歷50個對象自然是要慢很多的。
3)限制應(yīng)用的內(nèi)存
- 為了整個系統(tǒng)的內(nèi)存控制需要,Android系統(tǒng)為每一個應(yīng)用程序都設(shè)置一個硬性的Dalvik Heap Size最大限制閾值,這個閾值在不同的設(shè)備上會因為RAM大小不同而各有差異。如果你的應(yīng)用占用內(nèi)存空間已經(jīng)接近這個閾值,此時再嘗試分配內(nèi)存的話,很容易引發(fā)OutOfMemoryError錯誤。
- ActivityManager.getMemoryClass()可以用來查詢當(dāng)前應(yīng)用的Heap Size閾值,這個方法會返回一個整數(shù),表明應(yīng)用的Heap Size閾值是多少MB(Megabates)。
4)應(yīng)用切換操作
- Android系統(tǒng)并不會在用戶切換應(yīng)用的時候執(zhí)行交換內(nèi)存操作。Android會把那些不包含F(xiàn)oreground組件的應(yīng)用進程放到LRU Cache中。例如,當(dāng)用戶開始啟動一個應(yīng)用時,系統(tǒng)會為它創(chuàng)建一個進程。但是當(dāng)用戶離開此應(yīng)用,進程不會立即被銷毀,而是被放到系統(tǒng)的Cache當(dāng)中。如果用戶后來再切換回到這個應(yīng)用,此進程就能夠被馬上完整地恢復(fù),從而實現(xiàn)應(yīng)用的快速切換。
- 如果你的應(yīng)用中有一個被緩存的進程,這個進程會占用一定的內(nèi)存空間,它會對系統(tǒng)的整體性能有影響。因此,當(dāng)系統(tǒng)開始進入Low Memory的狀態(tài)時,它會由系統(tǒng)根據(jù)LRU的規(guī)則與應(yīng)用的優(yōu)先級,內(nèi)存占用情況以及其他因素的影響綜合評估之后決定是否被殺掉。
- 對于那些非foreground的進程,Android系統(tǒng)是如何判斷Kill掉哪些進程的問題,請參考Processes and Threads。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。