Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android —— 內存洩漏檢查

Android —— 內存洩漏檢查

編輯:關於Android編程

今天地鐵上看到一篇不錯的將內存洩漏簡單檢查的文章,覺得還不錯喲,內存洩漏確實是每個程序員頭疼的事情,這裡就多研究一下咯^^

一. 常見的垃圾回收算法

參看文章

引用計數法
引用計數法基本上最簡單的垃圾回收策略,它的核心思想是:
當有指針指向某實例時,計數加一, 當刪除一個指針時,計數減一,當計數為0時,說明該實例沒有引用可以被垃圾回收器回收。
這種回收策略的缺點是顯而易見的:
1.維護引用計數是有開銷的
2.計數的保存會消耗額外的空間
3.無法處理循環引用

標記清除
標記清除,顧名思義分為2步:1.標記 2.清除。標記清除會先從根掃描所有的可達對象,不可達的對象就是無用的垃圾對象。然後回收器集中清除垃圾對象。這種策略的缺點是: 1.標記整理的時候需要JVM停止其它工作 2.整理後產生了很多內存碎片

復制算法
復制算法的核心是:將內存空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存區間中的存活對象復制到另一塊,之後,清除正在使用的一塊中的所有對象,然後兩塊區域交換角色。缺點:
1.將內存折半,利用率低

標記壓縮算法
標記壓縮算法在標記清除算法的基礎上做了些改進,它在標記完所有的有效對象後,將有效的內存壓縮到一起,然後再清除其它區域。

分代算法
因為每一種算法都有各自的優略,所以根據對象的不同特點使用不同的回收算法能夠顯著提高垃圾回收的效率。分代算法正是基於這種思想產生的,像上一篇我們介紹的Java堆,一般有年輕代和老年代。年輕代的特點就是對象的存活時間短,所以這個時候選用復制算法是非常合適的。 而老年代中的對象一般不會被回收,這個時候如果也用復制算法顯然是不合適的, 這個時候JVM選擇了標記壓縮算法。
對於年輕代和老年代來說,一般年輕代的垃圾回收頻率高耗時短,老年代的回收頻率低耗時長。為了應對這種情況,JVM使用了一種卡表的數據結構。其中記錄某一區域的老年代對象是否持有年輕代對象的引用,如果沒有持有的話就不需要掃描老年代,可以加大年輕代的回收效率。

分區算法
分代算法按照對象的生命周期將對象分為了兩部分,而分區算法將整個堆空間劃分成不同的區域,每一個區域都獨立使用,獨立回收。這種算法的好處是可以控制一次回收的區域個數,這樣可以根據目標的停頓時間選擇合適的回收個數,從而達到減少GC停頓的目的。

二.Java的4種引用方式與GC的關系

參看博文
任何被強引用指向的對象都不能被回收;
弱引用在GC時一定會被回收;
軟引用只有在內存不足時才會被回收;
虛引用在任何時刻都可能會被回收,程序中可通過判斷引用隊列中是否已經加入虛引用來判斷對象是否將要被GC。

來看下面三組例子:

public class ReferenceTest {
WeakReference w;

public void test() {
    w = new WeakReference(new String("aaa"));
    System.gc();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(w.get());
}

}
答案是null,因為”aaa”這個對象只有w這一個弱引用指向它。

String a = new String(“aaa”);
w = new WeakReference(a);
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(w.get());
答案是”aaa”,因為”aaa”被一個強引用a指向,所以GC時不會被回收,因此w仍到得到這個對象。

String a = new String(“aaa”);
w = new WeakReference(a);
a = null;
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(w.get());
答案是null,因為”aaa”起初被一個強引用a指向,但後來這個強引用沒了,所以GC時”aaa”就被回收了。

三.內存洩漏

雖然垃圾回收器會幫我們干掉大部分無用的內存空間,但是對於還保持著引用,但邏輯上已經不會再用到的對象,垃圾回收器不會回收它們。這些對象積累在內存中,直到程序結束,就是我們所說的“內存洩漏”。
這裡我們來區別一下內存抖動:
內存抖動
在極短的時間內,分配大量的內存,然後又釋放它,這種現象就會造成內存抖動。典型地,在 View 控件的 onDraw 方法裡分配大量內存,又釋放大量內存,這種做法極易引起內存抖動,從而導致性能下降。因為 onDraw 裡的大量內存分配和釋放會給系統堆空間造成壓力,觸發 GC 工作去釋放更多可用內存,而 GC 工作起來時,又會吃掉寶貴的幀時間 (幀時間是 16ms) ,最終導致性能問題。

Q1:在Android開發測試中一般如何發現內存洩漏的發生呢?

答:
方法1:反復操作觀察內存變化

內存洩漏常見變現為程序使用時間越長,內存占用越多。那我們通過反復操作應用,比如反復點開/關閉頁面,觀察內存變化狀況是否一點點上漲,可以粗略地判斷是否有內存洩漏。觀察哪裡呢?
1)可以查看DDMS中的heap。
這裡寫圖片描述
2)在Android Studio中可以通過查看Android MonitZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcrSwv9o8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160914/20160914095400425.png" title="\" />

注:內存抖動和內存洩漏 對於內存波動的區別:
這裡寫圖片描述
這裡寫圖片描述

內存洩漏 我們通常可以利用 MAT 工具進行專業分析,這個我們後面來講解。
方法2:通過命令行查看內存詳細占用情況:

adb shell dumpsys meminfo -a “包名”

方法3:通過代碼檢測Activity洩漏 利用LeakCanary來檢測
LeakCanary 是一個用來檢查 Android 下內存洩漏的開源庫。不錯的介紹文章

如果有一個工具能自動完成這些事情,甚至在發生 OOM 之前,就把內存洩漏報告給你,那是多麼美好的一件事情啊。LeakCanary 就是用來干這個事情的。在測試你的 App 時,如果發生了內存洩漏,狀態欄上會有通知告訴你。logcat 上也會有相應的 log 通知你。哇塞,酷斃了!既然是開源,那我們直接來看看如何使用吧。
1)集成 LeakCanary 庫(在Android Studio)
在Studio中添加依賴,可以通過project structure,不要忘了設置debugCompile和releaseCompile ,最終將會在gradle中的依賴中多了這幾行代碼。

dependencies {
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
}

設置debug版和release版的原因:
在 debug 版本上,集成 LeakCanary 庫,並執行內存洩漏監測,而在 release 版本上,集成一個無操作的 wrapper ,這樣對程序性能就不會有影響。
2)初始化LeakCanary

我們需要繼承Application,在onCreate()裡調用LeakCanary.install(this)。LeakCanary.install() 返回一個配置好了的 RefWatcher 實例。
public class ExampleApplication extends Application {

    public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication)   context.getApplicationContext();
        return application.refWatcher;
    }

    private RefWatcher refWatcher;

    @Override public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
    }
}
然後,在AndroidManifest.xml注冊下。
 

3)監控Activity洩漏
我們經常把 Activity 當作為 Context 對象使用,在不同場合由各種對象引用 Activity。所以,Activity 洩漏是一個重要的需要檢查的內存洩漏之一。 在Application中,我們調用LeakCanary.install(this)。 這個方法返回一個配置好了的 RefWatcher 實例。它同時安裝了 ActivityRefWatcher 來監控 Activity 洩漏。所以,當我們初始化之後就可以自動監控 Activity 洩露,即當 Activity.onDestroy() 被調用之後,如果這個 Activity 沒有被銷毀,logcat 就會打印出信息告訴你內存洩漏發生。LeakCanary 的第一次分析可能會耗時較久,耐心等待。

4)監控 Fragment 洩漏

public abstract class BaseFragment extends Fragment {

    @Override 
    public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher =     ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);
    }
}

當 Fragment.onDestroy() 被調用之後,如果這個 fragment 實例沒有被銷毀,那麼就會從 logcat 裡看到相應的洩漏信息。

5)更多
LeakCanary 自動檢測 Activity 洩漏只支持 Android ICS 以上版本。因為 Application.registerActivityLifecycleCallbacks() 是在 API 14 引入的。如果要在 ICS 之前監測 Activity 洩漏,可以重載 Activity.onDestroy() 方法,然後在這個方法裡調用 RefWatcher.watch(this) 來實現。

上代碼來看一下吧
我創建了兩個activity,在第一個Activity中點擊按鈕進入SecondAcitivity,在SecondAcitivity中創建了匿名內部類,而且持有一個靜態的變量,這樣,恭喜,內存洩漏就離你不遠了。如果要詳細了解可能發生內存洩漏的幾種可能,請查看博主的下篇博文喽。
來來來 ,上代碼:
MainAcitivy:
public class MainActivity extends AppCompatActivity {
    private int i = 99;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = (Button) findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplicationContext(),SecondActivity.class);
                startActivity(intent);
            }
        });

    }
}

SecondActivity:

public class SecondActivity extends Activity {
    private static int index = 0;
    private static Object inner;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_second);
        createInnerClass();
        for (int i = 0; i < 40; i++) {
//            Log.i("dongmj","hello");
            index++;
        }
    }
    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }
}

// 查看打印的log
這裡寫圖片描述
顯然leakcanary提示你內存洩漏喽,而且已經精確到時由於SecondAsitivity中的inner類造成的,哇哦,,神奇的不得了。
更加神奇的是,在手機應用中會出現leaks的應用名稱,打開後顯示的是你的應用出現內存溢出的時間,並且可以點擊查看詳情。
這裡寫圖片描述

4 Hprof文件

在上面例子中我們發現log中提示,在sd卡中生成了hprof文件,根據這個文件你可以對內存洩漏進行分析。
如何導出:

File heapDumpFile = new File("heapdump.hprof");
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());

如何分析 hprof 文件

這是個比較大的話題,感興趣的可以移步另外一個開源庫 HAHA,它的祖先是 MAT。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved