介紹
在Android系統中,內存分配與釋放分配在必定程度上會影響App性能的—鑑於其使用的是相似於Java的GC回收機制,所以系統會以消耗必定的效率爲代價,進行垃圾回收。
在中國有句老話:」由儉入奢易,由奢返儉難」。而此諺語也彷佛正適應於Android的內存使用。GC回收機制給程序員省去了像C語言程序員那樣手動釋放內存的工做,可是也帶來了一系列的」雷」—動輒內存泄漏,再甚者稍微不慎就會OOM。
這篇文章將會介紹Android的內存管理機制並解釋幾種在此機制下對內存有影響的幾個比較關鍵的因素。另外,還會介紹如何提升內存管理、檢測並避免內存泄漏,以及如何分析內存分配狀況android
Here we go!!
Android內存管理機制
Android內存模型並無交換空間(swap space)的概念,而是使用分頁(paging)和內存映射(memory-mapping)管理內存,這意味着無論是分配新的對象仍是使用已有的映射頁這些內存仍然被佔據在RAM裏而不能被扇出。所以徹底釋放你app內存的惟一方式是釋放對象引用以便於能被垃圾回收器回收。
Dalvik虛擬機爲每個App分配相應大小的可用內存空間,從2M開始到32M(此最大值根據不一樣的廠商通常會有不一樣),不能否認,在當前國內各大手機廠商瘋狂的拼硬件的時代,這個每一個App的可用內存甚至被提升到了256M,這有效的避免了不少OOM的狀況,可是若是程序員所以就無論內存管理任意而爲,會爲此付出嚴重代價的(App高卸載率).
Android系統會將在後臺運行的App進程保存在一個LRU cache中(不懂的自行百度)。當系統內存緊張時,它會根據LRU的策略kill掉一些優先級比較低的進程。固然,究竟哪個App是當前佔用內存最大的程序也是它kill進程時所考慮的一個因素。若是你但願本身的App在後臺運行時能儘量長的」活着」,不被系統kill掉,就要好好的思考如何避免被kill。好比在App轉到後臺運行以前,儘量的將沒有用的內存給釋放掉,這樣會減小Android系統打印錯誤日誌甚至終止App的可能性。程序員
如何提升Android內存使用
Android系統是世界上使用率最高的手機系統。每一年都有成千上萬的年輕人轉入到開發Android系統的行列中,可是這些人中,能真正寫出穩定、可擴展性強的代碼的仍是少數。數據庫
如下是提升內存使用的幾條建議:
- 慎用橋接模式,雖然從程序的設計角度來看,抽象可以幫助咱們建立更加靈活的軟件架構。可是在手機系統中,這種設計模式有可能會形成不少反作用。除非大有必要,不然儘可能不要用橋接模式
- 避免使用枚舉Enum,一個Enum分配的空間是一個普一般量的兩倍,所以儘可能少使用枚舉
- 試着使用Android框架優化後的數據容器,譬如:SparseArray, SparseBooleanArray, 以及 LongSparseArray containers. 使用這些類來替代HashMap的使用。緣由是傳統的 HashMap 在內存上的實現十分的低效,由於它須要爲 HashMap 中每一項在內存中創建映射關係. 另外, SparseArray類很是高效由於它避免了對key和value的自動封箱. 萬事都有兩面性,這些個被優化過的容器也不例外,千萬記住SparseArray等容器並不適應於內部元素不少的集合,當集合的長度超過1000條時,使用SparseArray進行增刪改查的效率遠比HashMap低
- 避免建立不須要的對象。對於生命週期較短的臨時變量,儘可能想辦法規避掉每次都要去建立它,這樣GC回收被強制調用機會就會更少,留給Android系統進行UI渲染或者音頻加載的時間就會更多,從而避免了卡頓現象
- 檢測App內存中的可用堆的大小,在代碼中能夠經過動態的調用ActivityManager::getMemoryClass()方法來查詢你的App中的可用內存堆大小。若是系統檢測到須要分配的內存大小超過了此值,則會拋出OOM錯誤
- **能夠適當適應onTrimMemory回調方法。OnTrimMemory 回調是 Android 4.0 以後提供的一個API,這個 API 是提供給開發者的,它的主要做用是提示開發者在系統內存不足的時候,經過處理部分資源來釋放內存,從而避免被 Android 系統殺死。這樣應用在下一次啓動的時候,速度就會比較快。—詳情請參閱Android內存優化—OnTrimMemory
- 當使用Service應當當心當心再當心!當你須要啓動一個服務在後臺執行一項任務時,應當在其完成工做以後儘快的中止此服務。能夠考慮使用IntentService—當在子線程完成耗時操做以後,IntentService會自動中止並結束自身。然而在實際開發中常常會碰到須要服務去執行一項耗時比較長的任務,好比:音樂播放器,下載APP等等。像這樣的應用能夠分隔爲兩個進程:一個進程負責 UI 工做, 另一個則在後臺服務中運行其它的工做. 在AndroidManifest 文件中爲各個組件申明 android:process 屬性就能夠分隔爲不一樣的進程。注意一點:在後臺運行的Service絕對不能處理或者持有任何UI,不然系統可能會分配雙倍甚至三倍的空間來維護UI資源!!
- 當你加載 bitmap 時, 須要根據當前設備的分辨率加載相應分辨率的bitmap進入內存,若是下載下來的原圖分辨率比設備分辨率高則要壓縮它. 要當心bitmap的分辨率增長後所佔用的內存也要進行相應的增長(平方級increase2的增加), 由於它是根據x和y的大小來增長內存佔用的
- 使用代碼混淆工具 ProGuard 經過去除沒有用的代碼和經過語義模糊來重命名類, 字段和方法來縮小, 優化和混淆你的代碼. 使用它能使你的代碼更簡潔, 更少許的RAM映射頁.若是構建apk後你沒有作後續的任何處理(包括根據你的證書進行簽名), 你必須運行 zipalign 工具爲你的apk進行優化, 若是不這樣作會致使你的應用使用更多的內存,zipalign以後像資源這樣的東西不會再從apk中映射(mmap)入內存.注意:goole play store 不接受沒有進行zipalign的apk
針對以上幾條,後續會單獨再post幾篇blog單獨講解。windows
如何避免內存泄漏
程序員在分配內存時若是考慮到了上述9條建議,或許會給App在效率上帶來不小的收益,而且能夠在後臺時依然堅挺(更持久!)。 可是這一切的努力都會由於一個叫作內存泄漏的東東而萎了! 這玩意就如同可樂的存在同樣,少喝一點還能扛得住,可是多了的話。。你懂得! 如下是幾個常見的形成內存泄漏的狀況:設計模式
- 當查詢完數據庫以後,及時關閉Cursor對象。
- 記得在Activity的onPause方法中調用unregisterReceiver()方法,解註冊廣播
- 避免Content內存泄漏,好比在4.0.1以前的版本上不要講Drawer對象置爲static。當一個Drawable綁定到了View上,實際上這個View對象就會成爲這個Drawable的一個callback成員變量,上面的例子中靜態的sBackground持有TextView對象lable的引用,而lable只有Activity的引用,而Activity會持有其餘更多對象的引用。sBackground生命週期要長於Activity。當屏幕旋轉時,Activity沒法被銷燬,這樣就產生了內存泄露問題。
- 儘可能不要在Activity中使用非靜態內部類,由於非靜態內部類會隱式持有外部類實例的引用,當非靜態內部類的引用的聲明週期長於Activity的聲明週期時,會致使Activity沒法被GC正常回收掉。
- 謹慎使用線程Thread!!這條是不少人會犯的錯誤: Java中的Thread有一個特色就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對全部被激活狀態的線程都是持有強引用,致使GC永遠都沒法回收掉這些線程對象,除非線程被手動中止並置爲null或者用戶直接kill進程操做。因此當使用線程時,必定要考慮在Activity退出時,及時將線程也中止並釋放掉
- 使用Handler時,要麼是放在單獨的類文件中,要麼就是使用靜態內部類。由於靜態的內部類不會持有外部類的引用,因此不會致使外部類實例的內存泄露–詳情請參閱Android中Handler引發的內存泄露
如何分析內存的使用狀況
在Mac終端(windows的cmd)中,可使用adb logcat命令來查看或者統計內存的具體使用狀況,另外還能夠指定包名來查看相應App的內存使用狀況。除此以外,還可使用三方的工具來分析Android內存的使用狀況,好比:DDMS、MAT(Memory Analyzer tool).api
在adb logcat中,一般能看到GC相關的log以下圖所示
GC_Reason 觸發GC回收的緣由,可能包含如下幾種狀況:
- GC_FOR_ALLOC, 這個是說咱們的應用嘗試去分配內存而這時候和heap已經快滿了(不夠用了),這個時候系統會把咱們的應用停下來而後進行內存回收,一般heap size會增大
- GC_CONCURRENT,這個應該的當咱們的Heap size 快要被填滿的時候觸發的一個併發的內存回收
- GC_EXPLICIT,這個是主動調用系統gc方法觸發的GC(在DDMS 點擊GC就能夠看到)
- GC_HPROF_DUMP_HEAP 咱們在作內存分析建立HPROF(MAT能夠分析該文件)的時候會打印
Amount feed 表示本次垃圾收集釋放了多少內存
Heap_stats 當前空閒內存佔總內存的百分比
External memory stats 表示API 10及如下的外部分配內存,已分配內存/致使垃圾回收的閾值
Pause_time 應用暫停的時間
一般狀況下,生成的GC log越大,表示內存的分配與釋放發生的頻率越高,這種狀況下每每會很是影響用戶體驗!架構
使用DDMS查看並追蹤堆內存的分配狀況
經過DDMS,程序員能夠很輕鬆的檢測指定進程的內存分配狀況。你能夠經過「Heap」標籤查看最新的實時的堆內存信息,這樣能夠幫助你辨別出到底是哪個操做最有可能形成大量的內存分配。 「Allocation Tracker」 標籤顯示的是最近全部的內存分配—包含分配對象的類型,是在哪一個線程中分配等信息。一下圖片演示的是使用DDMS展現進程信息—包含了當前進程、對內存分配統計信息。併發