Android內(nèi)存管理及優(yōu)化


Random Access Memory(RAM)在任何軟件開發(fā)環(huán)境中都是非常重要的資源,但在物理內(nèi)存通常很有限的移動操作系統(tǒng)上更為重要。盡管Android Runtime (ART)和Dalvik虛擬機扮演了垃圾回收的角色箱歧,但這并不意味著我們可以忽略應用的內(nèi)存分配與釋放的時機和位置炎功。我們也需要避免引入Memory Leaks(內(nèi)存泄漏凤薛,通常由于在Gm需要回收一些釋放對象時扔持有對象引用在靜態(tài)成員變量里姓建,在釋放一些引用對象在需要回收時)。

本文會解釋Android是如何管理應用的內(nèi)存分配枉侧,以及在開發(fā)Android應用的時候如何主動的減少內(nèi)存的使用引瀑。

1狂芋、Android是如何管理內(nèi)存的

Android的運行時(ART)和Dalvik虛擬機使用分頁和內(nèi)存映射(mmapping)來管理內(nèi)存榨馁。這意味著,一個應用程序的任何內(nèi)存修改帜矾,不管是通過分配新的對象還是觸摸mmapped頁翼虫,仍駐留在內(nèi)存中,不能被調(diào)出屡萤。從應用程序釋放內(nèi)存的唯一方法是釋放應用所持有的引用對象珍剑,使內(nèi)存可用于垃圾收集器。有一個例外:沒有修改mmapped中的任何文件死陆,如代碼招拙,在系統(tǒng)在其他地方需要使用該內(nèi)存時被換出RAM唧瘾。

1. Garbage collection(垃圾回收)
一個管理內(nèi)存的環(huán)境,如ART或Dalvik虛擬機别凤,跟蹤每一個內(nèi)存分配饰序。一旦它確定一塊存儲器不再被使用的程序,它釋放回堆规哪,不需要開發(fā)者的任何干預求豫。對于托管內(nèi)存環(huán)境中回收未使用的內(nèi)存的機制被稱為Garbage collection(垃圾回收)垃圾回收有兩個目標:找到程序里未來不能被訪問的對象并回收這些對象所使用的資源。

2.shared memory(共享內(nèi)存)
為了適應在RAM中需要的一切诉稍,Android嘗試共享跨進程內(nèi)存的pages蝠嘉。它可以通過以下方式做到:

  • 每一個app的process都是從Zygote(受精卵)的進程中fork出來的。Zygote進程在系統(tǒng)啟動并且加載通用的framework的代碼與資源(比如ActivityThemes)時開始啟動杯巨。為了啟動一個新的程序進程蚤告,系統(tǒng)會fork Zygote進程生成一個新的process,然后在新的process中加載并運行app的代碼舔箭。這使得大多數(shù)的RAM pages被用來分配給framework的代碼與資源罩缴,并在應用的所有進程中進行共享。
  • 大多數(shù)static的數(shù)據(jù)被映射到一個進程中层扶。這種方式可以使數(shù)據(jù)不僅能夠在進程間進行共享箫章,而且使得它能夠在需要的時候被paged out。例如下面幾種static的數(shù)據(jù):
    • Dalvik code (by placing it in a pre-linked .odex file for direct mmapping

    • App resources (by designing the resource table to be a structure that can be mmapped and by aligning the zip entries of the APK)

    • Traditional project elements like native code in .so files.

    • 在很多地方镜会,Android通過分配的共享內(nèi)存(例如ashmem(匿名共享內(nèi)存)或者gralloc)來實現(xiàn)跨進程共享內(nèi)存檬寂。例如,window surfaces在app與screen compositor之間使用共享的內(nèi)存戳表,cursor buffers在content provider與client之間使用共享的內(nèi)存桶至。

3.分配和回收應用內(nèi)存
每一個應用進程的Dalvik 都與一個虛擬的內(nèi)存范圍(heapSize)。這定義了一個了了邏輯堆的大胸倚瘛(heapSize)镣屹,它可以隨著需要增長至系統(tǒng)為每個應用設(shè)定的大小限制。

heapSize和實際物理上的內(nèi)存數(shù)量是不等的价涝。當檢查app的堆時女蜈,Android會計算Proportional Set Size(PSS)的值,PSS是實際共享的內(nèi)存大小色瘩,記錄了那些和其他進程進行共享的內(nèi)存伪窖。

Dalvik堆和邏輯堆得大小(heapSize)并不吻合居兆,這意味著Android并沒有使用碎片處理去關(guān)閉空閑區(qū)域覆山。Android只有當堆末端有沒用的空間時才能收縮邏輯堆大小(heapsize),但系統(tǒng)仍然可以減少堆所占用的物理內(nèi)存大小泥栖。在垃圾回收之后簇宽,Dalvik會遍歷heap并找出不使用的pages勋篓,然后使用madvise把那些pages返回給kernal。因此魏割,成對的allocations與deallocations大塊的數(shù)據(jù)可以使得物理內(nèi)存能夠被正常的回收生巡。然而,回收碎片化的內(nèi)存則會使得效率低下很多见妒,因為那些碎片化的分配頁面也許會被其他地方所共享到孤荣。

4. 限制應用的內(nèi)存

為了維持多任務(wù)的功能環(huán)境,Android為每一個app都設(shè)置了一個硬性的heap size限制须揣。確切的heapsize限制隨著設(shè)備可用的RAM大小而有差異盐股,如果app的heapSize已經(jīng)達到了最大限制而嘗試分配更多的內(nèi)存就會引起OutOfMemoryError

如果想要查詢當前設(shè)備的heap size限制大小是多少耻卡,然后決定cache的大小疯汁。可以通過 getMemoryClass()來查詢卵酪。這個方法會返回一個整數(shù)幌蚊,表明你的app heap size限制是多少。
也可以通過手機目錄system>build.prop文件查看

5溃卡、切換應用

當用戶切換兩個應用時溢豆,Android會把那些不是前臺的應用()放在 least-recently used (LRU)cache里。比如說當用戶第一次啟動app時瘸羡,系統(tǒng)就會為他創(chuàng)建一個進程漩仙,但是當用戶退出應用是,之前創(chuàng)建的進程并沒有退出犹赖,系統(tǒng)將進程緩存起來队他,當用戶再返回到應用時,系統(tǒng)就會重用這個進程峻村,這樣會讓應用切換的更快麸折。

如果你的應用有一個被緩存的進程,雖然它沒有被使用粘昨,但它仍被保留在內(nèi)存中垢啼,這會影響系統(tǒng)的整體性能。當系統(tǒng)開始進入低內(nèi)存狀態(tài)時雾棺,它會殺掉Lrucache中最近使用最低的進程膊夹,系統(tǒng)也會把進程所占用的內(nèi)存進行釋放衬浑。

2捌浩、應用如何管理內(nèi)存

一些Android特性、Java類和代碼結(jié)構(gòu)傾向于使用更多的內(nèi)存工秩。我們可以通過提高代碼的效率來減少應用程序?qū)?nèi)存的使用

  • 減少Service的使用
    當我們需要啟動一個Service進行后來操作時尸饺,只有當需要服務(wù)執(zhí)行操作時再啟動服務(wù)进统,當服務(wù)執(zhí)行完畢時要及時銷毀服務(wù),否在我們在不經(jīng)意間就會造成內(nèi)存泄漏浪听。當一個Service已經(jīng)不需要了卻沒有銷毀是最糟糕的一個內(nèi)存管理錯誤螟碎。讓一個不需要運行的服務(wù)運行是最糟糕的一種內(nèi)存管理錯誤。

當我們啟動一個服務(wù)時迹栓,系統(tǒng)會傾向于一直保持服務(wù)所在的Process掉分,這使得Service所在的Process非常浪費,因為系統(tǒng)沒法把Service里所占用的沒有用的RAM給其他的Process克伊,這減少了系統(tǒng)能夠緩存的進程的數(shù)量酥郭,使應用間的切換更加低效。它甚至會導致系統(tǒng)內(nèi)存不穩(wěn)定愿吹,不能夠維持正在運行的服務(wù)不从。

一般情況下應該盡量少使用持久性的Service,因為它一直需要占用內(nèi)存。谷歌推薦我們使用 JobSchedulerIntentService(當處理完Intent后會自動停止)犁跪。

  • 使用高效的數(shù)據(jù)容器
    編程語言提供的一些類在移動設(shè)備上用效率并不是很高椿息,例如使用HashMap就會是內(nèi)存效率很低,因為它每一個映射都需要一個單獨的實例坷衍。
    Android框架包括幾個高效的數(shù)據(jù)容器,包括 SparseArray, SparseBooleanArray
    LongSparseArray.
    例如,SparseArray類更有效,因為他們避免系統(tǒng)的需要而去對key和value(有時會創(chuàng)建兩個)進行autobox(自動裝箱)寝优。

  • 注意代碼抽象
    開發(fā)人員通常使用抽象僅僅作為一個良好的編程實踐,因為抽象可以提高代碼的靈活性和維護。然而,抽象代價巨大:通常需要大量更多的需要執(zhí)行的代碼,需要更多的時間和更多的RAM代碼映射到內(nèi)存中枫耳。如果你的抽象類不提供一個重要的作用,你應該避免他們倡勇。

  • 使用nano protobufs序列化數(shù)據(jù)
    Protocol buffers是由谷歌設(shè)計用于序列化結(jié)構(gòu)化數(shù)據(jù)的語言中立的,與平臺無關(guān)的,可擴展的機制,和 XML類似嘉涌,但是更小妻熊,更快,更簡單仑最。如果你決定讓你的代碼使用nano protobufs 扔役,你應該一直使用nano protobufs在你的客戶端代碼里。常規(guī)protobufs生成非常冗長的代碼,這可能會導致應用產(chǎn)生多種問題比如說增加了內(nèi)存的使用,增加APK大小,執(zhí)行慢警医。

  • 避免內(nèi)存流失
    正如前面所說的亿胸,垃圾回收機制并不影響app的性能。然而許多耗時很短的垃圾回收events能夠迅速吃掉你的幀時間预皇。系統(tǒng)花費在垃圾回收上的時間越多侈玄,它去做其它事情的時間就越少,比如渲染或者音頻流吟温。

    通常序仙,內(nèi)存流失會引起大量的垃圾回收事件,在實踐中鲁豪,內(nèi)存流失描述了在指定時間內(nèi)分配的臨時對象的數(shù)量潘悼。

    例如律秃,你可能會內(nèi)分配多個臨時對象在一個for循環(huán)里,或者在onDraw方法里創(chuàng)建Paint或者是Bitmap治唤。這兩種情況棒动,都是快速的產(chǎn)生了大量的對象,這些都能很快消耗內(nèi)存宾添,迫使出現(xiàn)一個垃圾回收事件船惨。

    所以我們需要找到代碼流失比較高的地方,這樣才能解決問題缕陕。推薦使用 Analyze your RAM usage工具找內(nèi)存流失的地方掷漱。

  • ** 移除掉內(nèi)存占用比較多的資源和庫**
    我們的代碼里可能會有一些吞噬內(nèi)存的資源和庫卻不讓我們知道。Apk的整體規(guī)模包括第三方的庫和或者嵌入的資源會影響app內(nèi)存的占用榄檬。你可以從你的代碼中刪除任何多余的卜范,不必要的或臃腫的組件來提高應用程序的內(nèi)存消耗。

  • ** 減少Apk的大小**
    減少APk的整體規(guī)模都能夠顯著的降低app的內(nèi)存占用鹿榜。Bitmap的大小海雪,資源,幀動畫和第三方庫都會影響apk的大小舱殿。AndroidStudio和AndroidSDK提供了多種工具來幫助您降低資源和外部依賴的大小奥裸。詳情可以查看Reduce APK Size.

  • 謹慎使用依賴注入框架
    依賴注入框架如Guice或RoboGuice可以簡化你寫的代碼,并提供一個自適應環(huán)境沪袭,這是測試和其他配置更改非常有用湾宙。然而,依賴框架并不總是為移動設(shè)備優(yōu)化冈绊。

    例如侠鳄,這些框架往往通過掃描你的代碼注解初始化過程。這可需要大量代碼不必要地映射到RAM中死宣。系統(tǒng)分配這些映射到clean區(qū)讓Android可以刪除它們伟恶,這些只有當這些映射存在很長一段時間后才會發(fā)生。毅该。博秫。
    如果我們需要注解框架,可以使用 Dagger框架眶掌,Dagger沒有使用反射掃描應用的代碼挡育。Dagger的嚴格執(zhí)行意味著它可以在Android應用中使用,而不增加不必要的內(nèi)存使用情況朴爬。

  • ** 謹慎使用擴展類庫**
    擴展類庫的代碼往往不是為移動環(huán)境而編寫的即寒,當被用在移動客戶端上工作時可能比較低效。當您決定使用一個外部庫,您可能需要為移動設(shè)備優(yōu)化該庫蒿叠。計劃這項工作的前期,并決定在所有使用它之前分析代碼大小和內(nèi)存占用方面的庫蚣常。

原文鏈接:
https://developer.android.com/topic/performance/memory.html
https://developer.android.com/topic/performance/memory-overview.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末市咽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抵蚊,更是在濱河造成了極大的恐慌施绎,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贞绳,死亡現(xiàn)場離奇詭異谷醉,居然都是意外死亡,警方通過查閱死者的電腦和手機冈闭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門俱尼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萎攒,你說我怎么就攤上這事遇八。” “怎么了耍休?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵刃永,是天一觀的道長。 經(jīng)常有香客問我羊精,道長斯够,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任喧锦,我火速辦了婚禮读规,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘燃少。我一直安慰自己掖桦,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布供汛。 她就那樣靜靜地躺著枪汪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怔昨。 梳的紋絲不亂的頭發(fā)上球拦,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音哈垢,去河邊找鬼案疲。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的越庇。 我是一名探鬼主播罩锐,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卤唉!你這毒婦竟也來了涩惑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤桑驱,失蹤者是張志新(化名)和其女友劉穎竭恬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熬的,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡痊硕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了押框。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岔绸。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖橡伞,靈堂內(nèi)的尸體忽然破棺而出亭螟,到底是詐尸還是另有隱情,我是刑警寧澤骑歹,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布预烙,位于F島的核電站,受9級特大地震影響道媚,放射性物質(zhì)發(fā)生泄漏扁掸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一最域、第九天 我趴在偏房一處隱蔽的房頂上張望谴分。 院中可真熱鬧,春花似錦镀脂、人聲如沸牺蹄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沙兰。三九已至,卻和暖如春翘魄,著一層夾襖步出監(jiān)牢的瞬間鼎天,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工暑竟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留斋射,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像罗岖,于是被迫代替她去往敵國和親涧至。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

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