http://blog.csdn.net/guolin_blog/article/details/42238633java
問題說明ide
因爲Android是爲移動設備開發的操做系統,咱們在開發應用程序的時候應當始終把內存問題充分考慮在內。雖然Android系統擁有垃圾自動回收機制,但這並不意味着咱們就能夠徹底忽略什麼時候去分配或釋放內存。即便咱們在寫程序的時候,會去注意這個問題,仍是會頗有可能出現內存泄露或其它類型的內存問題。因此,惟一可以解決問題的辦法,就是嘗試去分析應用程序的內存使用狀況。工具
答題技巧學習
雖然說如今的手機內存都已經很是大了,可是咱們你們都知道,系統是不可能將全部的內存都分配給咱們的應用程序的。沒錯,每一個程序都會有可以使用的內存上限,這被稱爲堆大小(Heap Size)。不一樣的手機,堆大小也不盡相同,隨着如今硬件設備不斷提升,堆大小也已經由Nexus One時的32MB,變成了Nexus 5時的192MB。若是你們想要知道本身手機的堆大小是多少,能夠調用以下代碼:操作系統
[java] view plaincopyprint?.net
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);插件
int heapSize = manager.getMemoryClass();線程
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();
結果是以MB爲單位進行返回的,咱們在開發應用程序時所使用的內存不能超出這個限制,不然就會出現OutOfMemoryError。日誌
若是咱們想要更加清楚地實時知曉當前應用程序的內存使用狀況,咱們須要經過DDMS中提供的工具來實現。打開DDMS界面,在左側面板中選擇你要觀察的應用程序進程,而後點擊Update Heap按鈕,接着在右側面板中點擊Heap標籤,以後不停地點擊Cause GC按鈕來實時地觀察應用程序內存的使用狀況便可,以下圖所示:orm
接着繼續操做咱們的應用程序,而後繼續點擊Cause GC按鈕,若是你發現反覆操做某一功能會致使應用程序內存持續增高而不會降低的話,那麼就說明這裏頗有可能發生內存泄漏了。
你們須要知道的是,Android中的垃圾回收機制並不能防止內存泄漏的出現,致使內存泄漏最主要的緣由就是某些長存對象持有了一些其它應該被回收的對象的引用,致使垃圾回收器沒法去回收掉這些對象,那也就出現內存泄漏了。好比說像Activity這樣的系統組件,它又會包含不少的控件甚至是圖片,若是它沒法被垃圾回收器回收掉的話,那就算是比較嚴重的內存泄漏狀況了。
下面咱們來模擬一種Activity內存泄漏的場景,內部類相信你們都有用過,若是咱們在一個類中又定義了一個非靜態的內部類,那麼這個內部類就會持有外部類的引用,以下所示:
[java] view plaincopyprint?
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakClass leakClass = new LeakClass();
}
class LeakClass {
}
......
}
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakClass leakClass = new LeakClass();
}
class LeakClass {
}
......
}
目前來看,代碼仍是沒有問題的,由於雖然LeakClass這個內部類持有MainActivity的引用,可是隻要它的存活時間不會長於MainActivity,就不會阻止MainActivity被垃圾回收器回收。那麼如今咱們來將代碼進行以下修改:
[java] view plaincopyprint?
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakClass leakClass = new LeakClass();
leakClass.start();
}
class LeakClass extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
......
}
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakClass leakClass = new LeakClass();
leakClass.start();
}
class LeakClass extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
......
}
這下就有點不太同樣了,咱們讓LeakClass繼承自Thread,而且重寫了run()方法,而後在MainActivity的onCreate()方法中去啓動LeakClass這個線程。而LeakClass的run()方法中運行了一個死循環,也就是說這個線程永遠都不會執行結束,那麼LeakClass這個對象就一直不能獲得釋放,而且它持有的MainActivity也將沒法獲得釋放,那麼內存泄露就出現了。
如今咱們能夠將程序運行起來,而後不斷地旋轉手機讓程序在橫屏和豎屏之間切換,由於每切換一次Activity都會經歷一個從新建立的過程,而前面建立的Activity又沒法獲得回收,那麼長時間操做下咱們的應用程序所佔用的內存就會愈來愈高,最終出現OutOfMemoryError。
下面我貼出一張不斷切換橫豎屏時GC日誌打印的結果圖,以下所示:
能夠看到,應用程序所佔用的內存是在不斷上升的。最可怕的是,這些內存一旦升上去了就永遠不會再降下來,直到程序崩潰爲止,由於這部分泄露的內存一直都沒法被垃圾回收器回收掉。
那麼經過上面學習DDMS工具這種方式,如今咱們已經能夠比較輕鬆地發現應用程序中是否存在內存泄露的現象了。可是若是真的出現了內存泄露,咱們應該怎麼定位到具體是哪裏出的問題呢?這就須要藉助一個內存分析工具了,叫作Memory Analyzer Tool(MAT)。這個工具分爲Eclipse插件版和獨立版兩種,若是你是使用Eclipse開發的,那麼可使用插件版MAT,很是方便。若是你是使用Android Studio開發的,那麼就只能使用獨立版的MAT了。
那麼接下來咱們開始學習如何去分析內存泄露的緣由,首先仍是進入到DDMS界面,而後在左側面板選中咱們要觀察的應用程序進程,接着點擊Dump HPROF file按鈕,以下圖所示:
點擊這個按鈕以後須要等待一段時間,而後會生成一個HPROF文件,這個文件記錄着咱們應用程序內部的全部數據。可是目前MAT仍是沒法打開這個文件的,咱們還須要將這個HPROF文件從Dalvik格式轉換成J2SE格式,使用hprof-conv命令就能夠完成轉換工做,以下所示:
[plain] view plaincopyprint?
hprof-conv dump.hprof converted-dump.hprof //直接進入hprof-conv坐在目錄執行該命令
hprof-conv命令文件存放於<Android Sdk>/platform-tools目錄下面。另外若是你是使用的插件版的MAT,也能夠直接在Eclipse中打開生成的HPROF文件,不用通過格式轉換這一步。
好的,接下來咱們就能夠來嘗試使用MAT工具去分析內存泄漏的緣由了,這裏須要提醒你們的是,MAT並不會準確地告訴咱們哪裏發生了內存泄漏,而是會提供一大堆的數據和線索,咱們須要本身去分析這些數據來去判斷究竟是不是真的發生了內存泄漏。那麼如今運行MAT工具,而後選擇打開轉換事後的converted-dump.hprof文件,以下圖所示:
MAT中提供了很是多的功能,這裏咱們只要學習幾個最經常使用的就能夠了。上圖最中央的那個餅狀圖展現了最大的幾個對象所佔內存的比例,這張圖中提供的內容並很少,咱們能夠忽略它。在這個餅狀圖的下方就有幾個很是有用的工具了,咱們來學習一下。
Histogram能夠列出內存中每一個對象的名字、數量以及大小。
Dominator Tree會將全部內存中的對象按大小進行排序,而且咱們能夠分析對象之間的引用結構。
通常最經常使用的就是以上兩個功能了,那麼咱們先從Dominator Tree開始學起。如今點擊Dominator Tree,結果以下圖所示:
這張圖包含的信息很是多,我來帶着你們一塊兒解析一下。首先Retained Heap表示這個對象以及它所持有的其它引用(包括直接和間接)所佔的總內存,所以從上圖中看,前兩行的Retained Heap是最大的,咱們分析內存泄漏時,內存最大的對象也是最應該去懷疑的。
另外你們應該能夠注意到,在每一行的最左邊都有一個文件型的圖標,這些圖標有的左下角帶有一個紅色的點,有的則沒有。帶有紅點的對象就表示是能夠被GC Roots訪問到的,根據上面的講解,能夠被GC Root訪問到的對象都是沒法被回收的。那麼這就說明全部帶紅色的對象都是泄漏的對象嗎?固然不是,由於有些對象系統須要一直使用,原本就不該該被回收。咱們能夠注意到,上圖當中全部帶紅點的對象最右邊都有寫一個System Class,說明這是一個由系統管理的對象,並非由咱們本身建立並致使內存泄漏的對象。
那麼上圖中就沒法看出內存泄漏的緣由了嗎?確實,內存泄漏原本就不是這麼容易找出的,咱們還須要進一步進行分析。上圖當中,除了帶有System Class的行以外,最大的就是第二行的Bitmap對象了,雖然Bitmap對象如今不能被GC Roots訪問到,但不表明着Bitmap所持有的其它引用也不會被GC Roots訪問到。如今咱們能夠對着第二行點擊右鍵 -> Path to GC Roots -> exclude weak references,爲何選擇exclude weak references呢?由於弱引用是不會阻止對象被垃圾回收器回收的,因此咱們這裏直接把它排除掉,結果以下圖所示:
能夠看到,Bitmap對象通過層層引用以後,到了MainActivity$LeakClass這個對象,而後在圖標的左下角有個紅色的圖標,就說明在這裏能夠被GC Roots訪問到了,而且這是由咱們本身建立的Thread,並非System Class了,那麼因爲MainActivity$LeakClass能被GC Roots訪問到致使不能被回收,致使它所持有的其它引用也沒法被回收了,包括MainActivity,也包括MainActivity中所包含的圖片。
經過這種方式,咱們就成功地將內存泄漏的緣由找出來了。這是Dominator Tree中比較經常使用的一種分析方式,即搜索大內存對象通向GC Roots的路徑,由於內存佔用越高的對象越值得懷疑。
接下來咱們再來學習一下Histogram的用法,回到Overview界面,點擊Histogram,結果以下圖所示:
這裏是把當前應用程序中全部的對象的名字、數量和大小所有都列出來了,須要注意的是,這裏的對象都是隻有Shallow Heap而沒有Retained Heap的,那麼Shallow Heap又是什麼意思呢?就是當前對象本身所佔內存的大小,不包含引用關係的,好比說上圖當中,byte[]對象的Shallow Heap最高,說明咱們應用程序中用了不少byte[]類型的數據,好比說圖片。能夠經過右鍵 -> List objects -> with incoming references來查看具體是誰在使用這些byte[]。
那麼經過Histogram又怎麼去分析內存泄漏的緣由呢?固然其實也能夠用和Dominator Tree中比較類似的方式,即分析大內存的對象,好比上圖中byte[]對象內存佔用很高,咱們經過分析byte[],最終也是能找到內存泄漏所在的。
好了,這大概就是MAT工具最經常使用的一些用法了,固然這裏還要提醒你們一句,工具是死的,人是活的,MAT也沒有辦法保證必定能夠將內存泄漏的緣由找出來,仍是須要咱們對程序的代碼有足夠多的瞭解,知道有哪些對象是存活的,以及它們存活的緣由,而後再結合MAT給出的數據來進行具體的分析,這樣纔有可能把一些隱藏得很深的問題緣由給找出來。