ANDROID記憶體優化(大彙總——上)

yangxi_001發表於2015-03-16

轉載請註明本文出自大苞米的部落格(http://blog.csdn.net/a396901990),謝謝支援!


寫在最前:

本文的思路主要借鑑了2014年AnDevCon開發者大會的一個演講PPT,加上把網上搜集的各種記憶體零散知識點進行彙總、挑選、簡化後整理而成。

所以我將本文定義為一個工具類的文章,如果你在ANDROID開發中遇到關於記憶體問題,或者馬上要參加面試,或者就是單純的學習或複習一下記憶體相關知識,都歡迎閱讀。(本文最後我會盡量列出所參考的文章)。



記憶體簡介:

RAM(random access memory)隨機存取儲存器。說白了就是記憶體。

一般Java在記憶體分配時會涉及到以下區域:

暫存器(Registers):速度最快的儲存場所,因為暫存器位於處理器內部我們在程式中無法控制

棧(Stack):存放基本型別的資料和物件的引用,但物件本身不存放在棧中,而是存放在堆中

堆(Heap):堆記憶體用來存放由new建立的物件和陣列。在堆中分配的記憶體,由Java虛擬機器的自動垃圾回收器(GC)來管理。

靜態域(static field):  靜態儲存區域就是指在固定的位置存放應用程式執行時一直存在的資料,Java在記憶體中專門劃分了一個靜態儲存區域來管理一些特殊的資料變數如靜態的資料變數

常量池(constant pool):虛擬機器必須為每個被裝載的型別維護一個常量池。常量池就是該型別所用到常量的一個有序集和,包括直接常量(string,integer和floating point常量)和對其他型別,欄位和方法的符號引用。

非RAM儲存:硬碟等永久儲存空間


堆疊特點對比:

由於篇幅原因,下面只簡單的介紹一下堆疊的一些特性。

:當定義一個變數時,Java就在棧中為這個變數分配記憶體空間,當該變數退出該作用域後,Java會自動釋放掉為該變數所分配的記憶體空間,該記憶體空間可以立即被另作他用。

:當堆中的new產生陣列和物件超出其作用域後,它們不會被釋放,只有在沒有引用變數指向它們的時候才變成垃圾,不能再被使用。即使這樣,所佔記憶體也不會立即釋放,而是等待被垃圾回收器收走。這也是Java比較佔記憶體的原因。


存取速度比堆要快,僅次於暫存器。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。

:堆是一個執行時資料區,可以動態地分配記憶體大小,因此存取速度較慢。也正因為這個特點,堆的生存期不必事先告訴編譯器,而且Java的垃圾收集器會自動收走這些不再使用的資料。


:棧中的資料可以共享, 它是由編譯器完成的,有利於節省空間。

例如:需要定義兩個變數int a = 3;int b = 3;

編譯器先處理int a = 3;首先它會在棧中建立一個變數為a的引用,然後查詢棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3。接著處理int b = 3;在建立完b的引用變數後,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。這時,如果再a=4;那麼編譯器會重新搜尋棧中是否有4值,如果沒有,則將4存放進來,並讓a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。

:例如上面棧中a的修改並不會影響到b, 而在堆中一個物件引用變數修改了這個物件的內部狀態,會影響到另一個物件引用變數。


記憶體耗用名詞解析:

VSS - Virtual Set Size 虛擬耗用記憶體(包含共享庫佔用的記憶體)

RSS - Resident Set Size 實際使用實體記憶體(包含共享庫佔用的記憶體)

PSS - Proportional Set Size 實際使用的實體記憶體(比例分配共享庫佔用的記憶體)

USS - Unique Set Size 程式獨自佔用的實體記憶體(不包含共享庫佔用的記憶體)

一般來說記憶體佔用大小有如下規律:VSS >= RSS >= PSS >= USS


OOM:


記憶體洩露可以引發很多的問題:

1.程式卡頓,響應速度慢(記憶體佔用高時JVM虛擬機器會頻繁觸發GC)

2.莫名消失(當你的程式所佔記憶體越大,它在後臺的時候就越可能被幹掉。反之記憶體佔用越小,在後臺存在的時間就越長)

3.直接崩潰(OutOfMemoryError)


ANDROID記憶體面臨的問題:

1.有限的堆記憶體,原始只有16M

2.記憶體大小消耗等根據裝置,作業系統等級,螢幕尺寸的不同而不同

3.程式不能直接控制

4.支援後臺多工處理(multitasking)

5.執行在虛擬機器之上


5R:

本文主要通過如下的5R方法來對ANDROID記憶體進行優化:

1.Reckon(計算)

首先需要知道你的app所消耗記憶體的情況,知己知彼才能百戰不殆

2.Reduce(減少)

消耗更少的資源

3.Reuse(重用)

當第一次使用完以後,儘量給其他的使用

5.Recycle(回收)

返回資源

4.Review(檢查)

回顧檢查你的程式,看看設計或程式碼有什麼不合理的地方。


Reckon (計算):

瞭解自己應用的記憶體使用情況是很有必要的。如果當記憶體使用過高的話就需要對其進行優化,因為更少的使用記憶體可以減少ANDROID系統終止我們的程式的機率,也可以提高多工執行效率和體驗效果。

下面從系統記憶體(system ram)和堆記憶體(heap)兩個方面介紹一些檢視和計算記憶體使用情況的方法:


System Ram(系統記憶體):

觀察和計算系統記憶體使用情況,可以使用Android提供給我們的兩個工具procstatsmeminfo。他們一個側重於後臺的記憶體使用,另一個是執行時的記憶體使用。

Process Stats: 

Android 4.4 KitKat 提出了一個新系統服務,叫做procstats。它將幫助你更好的理解你app在後臺(background)時的記憶體使用情況。
Procstats可以去監視你app在一段時間的行為,包括在後臺執行了多久,並在此段時間使用了多少記憶體。從而幫助你快速的找到應用中不效率和不規範的地方去避免影響其performs,尤其是在低記憶體的裝置上執行時。
你可以通過adb shell命令去使用procstats(adb shell dumpsys procstats --hours 3),或者更方便的方式是執行Process Stats開發者工具(在4.4版本的手機中點選Settings > Developer options > Process Stats)

點選單個條目還可以檢視詳細資訊

meminfo:
Android還提供了一個工具叫做meminfo。它是根據PSS標準 (Proportional Set Size——實際實體記憶體)計算每個程式的記憶體使用並且按照重要程度排序。
你可以通過命令列去執行它:(adb shell dumpsys meminfo)或者使用在裝置上點選Settings > Apps > Running(與Procstats不用,它也可以在老版本上執行)

更多關於Procstatsmeninfo的介紹可以參考我翻譯的一篇文章:Process Stats:瞭解你的APP如何使用記憶體


Heap(堆記憶體):

在程式中可以使用如下的方法去查詢記憶體使用情況


ActivityManager#getMemoryClass()

查詢可用堆記憶體的限制

3.0(HoneyComb)以上的版本可以通過largeHeap=“true”來申請更多的堆記憶體(不過這算作“作弊”)


ActivityManager#getMemoryInfo(ActivityManager.MemoryInfo)
得到的MemoryInfo中可以檢視如下Field的屬性:
availMem:表示系統剩餘記憶體
lowMemory:它是boolean值,表示系統是否處於低記憶體執行
hreshold:它表示當系統剩餘記憶體低於好多時就看成低記憶體執行

android.os.Debug#getMemoryInfo(Debug.MemoryInfo memoryInfo)

得到的MemoryInfo中可以檢視如下Field的屬性:

dalvikPrivateDirty: The private dirty pages used by dalvik。
dalvikPss :The proportional set size for dalvik.
dalvikSharedDirty The shared dirty pages used by dalvik.
nativePrivateDirty The private dirty pages used by the native heap.
nativePss The proportional set size for the native heap.
nativeSharedDirty :The shared dirty pages used by the native heap.
otherPrivateDirty The private dirty pages used by everything else.
otherPss :The proportional set size for everything else.
otherSharedDirty :The shared dirty pages used by everything else.

dalvik是指dalvik所使用的記憶體
native是被native堆使用的記憶體。應該指使用C\C++在堆上分配的記憶體
other:是指除dalvik和native使用的記憶體。但是具體是指什麼呢?至少包括在C\C++分配的非堆記憶體,比如分配在棧上的記憶體。
private:是指私有的。非共享的。
share:是指共享的記憶體
PSS實際使用的實體記憶體(比例分配共享庫佔用的記憶體)
 PrivateDirty它是指非共享的,又不能換頁出去(can not be paged to disk )的記憶體的大小。比如Linux為了提高分配記憶體速度而緩衝的小物件,即使你的程式結束,該記憶體也不會釋放掉,它只是又重新回到緩衝中而已。
SharedDirty:參照PrivateDirty我認為它應該是指共享的,又不能換頁出去(can not be paged to disk )的記憶體的大小。比如Linux為了提高分配記憶體速度而緩衝的小物件,即使所有共享它的程式結束,該記憶體也不會釋放掉,它只是又重新回到緩衝中而已。

android.os.Debug#getNativeHeapSize()

返回的是當前程式navtive堆本身總的記憶體大小

android.os.Debug#getNativeHeapAllocatedSize()

返回的是當前程式navtive堆中已使用的記憶體大小

android.os.Debug#getNativeHeapFreeSize()

返回的是當前程式navtive堆中已經剩餘的記憶體大小


Memory Analysis Tool(MAT):

通常記憶體洩露分析被認為是一件很有難度的工作,一般由團隊中的資深人士進行。不過,今天我們要介紹的 MAT(Eclipse Memory Analyzer)被認為是一個“傻瓜式“的堆轉儲檔案分析工具,你只需要輕輕點選一下滑鼠就可以生成一個專業的分析報告。

如下圖:


關於詳細的MAT使用我推薦下面這篇文章:使用 Eclipse Memory Analyzer 進行堆轉儲檔案分析



寫在最後:

我準備將文章分為上、中、下三部分。現在已經全部完成:

記憶體簡介,Recoken(計算)請看ANDROID記憶體優化(大彙總——上)

Reduce(減少),Reuse(重用) 請看:ANDROID記憶體優化(大彙總——中)

Recycle(回收), Review(檢查) 請看:ANDROID記憶體優化(大彙總——全)


寫這篇文章的目的就是想弄一個大彙總,將零散的記憶體知識點總結一下,如果有錯誤、不足或建議都希望告訴我。


參考文章:

AnDevCon開發者大會演講PPT:Putting Your App on a Memory Diet

深入Java核心 Java記憶體分配原理精講(http://developer.51cto.com/art/201009/225071.htm)

Process Stats: Understanding How Your App Uses RAM(http://blog.csdn.net/a396901990/article/details/38390135)

Android中如何檢視記憶體(http://blog.csdn.net/hudashi/article/details/7050897)

Android記憶體效能優化(內部資料總結)(http://www.2cto.com/kf/201405/303276.html)

相關文章