Memory Leak検出神器--LeakCanary初探査


以前の記事でAndroidメモリの漏洩のいくつかのケースでは、開発でよく見られるメモリの漏洩問題について言及したことがありますが、あまりにも軽率です.年明け早々、仕事が本格化していないので、Githubのオープンソース大手SquareのLeakCanaryを見て、会社のプロジェクトのテスト環境で手を練習して、プロジェクトに存在するメモリの漏洩を見つけようとしました.前回とは異なり、Javaのメモリ領域やゴミ回収メカニズムについてお話しし、LeakCanaryのアプリケーションについてお話しし、プロジェクトで遭遇した実例で終わります.
Javaのメモリモデル
LeakCanaryでは、Javaプログラムの実行時のスタックとスタックに主に関心を持っています.スタックはオブジェクトを格納する場所であり、スタックは参照を格納する場所です.リファレンスは、オブジェクトのハンドルまたはオブジェクトのアドレスによってオブジェクトに関連付けられます.ごみの回収は山の上で発生します.
Javaごみ回収アルゴリズム
ごみ回収アルゴリズムには多くの種類があります.ここでJavaでよく見られるゴミ回収アルゴリズムを紹介する:ゴミ回収器(GC)はスタック上のいくつかの参照に関連付けられたオブジェクトをルートノード(GC Root)とし、これらの参照に基づいてそれに関連付けられたオブジェクトを検索し、検索したノードからなる経路をGCチェーンと呼ぶ.例えば3つのクラスA,B,Cがあり、そのうちAはBのアプリケーションを持ち、BはCの参照を持ち、
public class A {

    public A(B b)
    {
        this.b = b;
    }
    private B  b;
}

public class B {

    public B(C c){
        this.c = c;
    }

    private C c;
}

public class C {
}

実行時:
    C c = new C();
    B b  = new B(c);
    A a= new A(b);

aを参照することでCのオブジェクトを見つけることができ,このチェーンはGCチェーンとして利用できる.
  オブジェクトがGC Rootからパスがある場合、このオブジェクトが参照されていることを示します.GCはこのような相手に対して「ネットが開く」.オブジェクトにGC Rootがない場合、GCはこれらのオブジェクトにマークを付け、後で回収するのに便利です.
ここでメモリ漏洩について再紹介する必要があると述べた.オブジェクトの「使命が完了した」場合、GCは、オブジェクトのメモリ領域の一部を回収する必要があります.たとえば、1つのメソッドにはローカル変数Aが含まれています.このメソッドが実行された後、Aがすぐに回収されることを望んでいますが、いくつかの理由で回収されていないため、メモリ漏洩が発生したと言います.なぜメモリが漏洩したのですか?あくまでこの時点でGC Rootからこの対象に到達できるからです.私たちのAndroidにとって、Androidの多くのコンポーネントにはライフサイクルの概念があります.例えば、Activity、Fragmentです.これらのコンポーネントのライフサイクルが終了した場合(onDestroyメソッドがコールバックされる)、これらのコンポーネントは回収されるべきである.しかし、Activityが比較的長い匿名の内部クラスによって参照され、staticオブジェクトによって参照され、Handler(一般的にはHandlerがpostDelayメソッドを呼び出した)によって参照されるなどの理由がある.
Androidはプロセスごとにメモリの使用量に制限があり、以前は16 MB以内だったので、メモリの使用に注意する必要があります.メモリ漏洩によりオブジェクトやAndroidコンポーネント(通常は多くの他の参照が含まれており、メモリの占有量が大きい)が回収されず、プログラムのセキュリティが極めて危険にさらされ、ユーザーがメモリ漏洩を引き起こす動作を繰り返し、メモリが短時間で急激に膨張し、最後にプログラムがフラッシュバックする「悲惨な結末」になる可能性がある..しかし、このような結末は私たちが望んでいるものではありません.だから、プログラムにメモリの漏洩を起こさないようにしなければなりません.メモリ漏洩のため、空のポインタのようなエラーが直接投げ出されることはありません.一般のプログラマーはメモリ漏洩による危険性を発見することは難しいです.統計によると、OOMの異常の94%はメモリの漏洩によるものだ.そのため、メモリの漏洩を解決することは、Androidプログラマーが直面しなければならない話題です.
メモリ漏洩検出神器LeakCanary
LeakCananryは、プログラム内のメモリ漏洩を検出するためのオープンソース大手Squareのオープンソース製品です.使いやすく、操作が簡単で、広範なアンドロイドプログラマーの必須神器です.GItHUBプロジェクトアドレス.
統合LeakCanary
  会社のプロジェクトはまだEclipseの上で開発されているので、ここではどのようにEclipseの中で統合するかを話しています.まず、Eclipse用のLeakCanaryをダウンロードします.プロジェクトアドレス.ここで著者の勤勉な労働に感謝する.その後、EclipseでダウンロードしたパッケージimportをEclipseワークスペースにダウンロードします.Androidのライブラリとして使用します.次に、LeakCanaryのサービスとActivityをプロジェクトにコピーします.サービスとActivityの名前を全クラス名に変更してください.変更されたリストファイルは、次のようになります.
.........
.........
      
.........

  

        <service
            android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"
            android:enabled="false"
            android:process=":leakcanary" />
        <service
            android:name="com.squareup.leakcanary.DisplayLeakService"
            android:enabled="false" />

        <activity
            android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
            android:enabled="false"
            android:icon="@drawable/__leak_canary_icon"
            android:label="@string/__leak_canary_display_activity_label"
            android:taskAffinity="com.squareup.leakcanary"
            android:theme="@style/__LeakCanary.Base" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>

これでLeakCanaryの統合が完了しました.
プロジェクトでのLeakCanaryの使用
ApplicationでLeakCanaryを初期化し、BaseActivityまたはBaseFragmentのonDestroyでこのクラスを監視する必要があります.コード:
    /**
     *            applicaton     
     */
    private void initRefWatcher() {
        this.refWatcher = LeakCanary.install(this);
    }

    //BaseActivity  BaseFragment   
    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher  refWatcher = MentorNowApplication.getRefWatcher(this);
        refWatcher.watch(this);
    }

これにより、私たちのプロジェクトを検査することができます.
ケース列
以下、私は私たちのプロジェクトのメモリ漏洩事件の列を持って具体的な使用を説明します.(前提はあなたのプロジェクトが正しくLeakCanaryを統合していることです).メモリリークが発生したコードを貼り付け、修正したコードも貼り付けます.メモリリークが発生したコード:プロジェクトでは、タイムバスEventBusを使用してデカップリングしました.EventBUsを使用するには、先に登録する必要があります.ページが破棄された場合、私たちは先に逆登録する必要があります.これは、EventBusの特定の設計によるもので、EventBusのライフサイクルはアプリケーション全体のライフサイクルと同じです.次に、LeakCananryを使用して、未登録によるFragmentメモリの漏洩を検出します.LeakCananryで得られたLog情報は、02-17 14:40:10.219:D/LeakCanary(29354):*com.mentornow.MainActivity has leaked: 02-17 14:40:10.219: D/LeakCanary(29354): * GC ROOT static event.EventBus.defaultInstance 02-17 14:40:10.220: D/LeakCanary(29354): * references event.EventBus.typesBySubscriber 02-17 14:40:10.220: D/LeakCanary(29354): * references java.util.HashMap.table 02-17 14:40:10.220: D/LeakCanary(29354): * references array java.util.HashMap HashMapEntry[].[3]02−1714:40:10.220:D/LeakCanary(29354):∗referencesjava.util.HashMap HashMapEntry.key 02-17 14:40:10.220: D/LeakCanary(29354): * references com.mentornow.fragment.DiscoverFragment.gv 02-17 14:40:10.220: D/LeakCanary(29354): * references com.mentornow.view.MyGridView.mContext 02-17 14:40:10.220: D/LeakCanary(29354): * leaks com.mentornow.MainActivity instance 02-17 14:40:10.220: D/LeakCanary(29354): * Reference Key: fef0c426-0096-475b-9f5c-cb193fa7cecd 02-17 14:40:10.220: D/LeakCanary(29354): * Device: motorola motorola XT1079 thea_retcn_ds 02-17 14:40:10.220: D/LeakCanary(29354): * Android Version: 5.0.2 API: 21 LeakCanary: 02-17 14:40:10.220: D/LeakCanary(29354): * Durations: watch=5042ms, gc=196ms, heap dump=2361ms, analysis=26892ms
解析ログ
最初の文は、MainActivityにメモリの漏洩が発生したことを明確に示しています.2番目の文でメモリが漏洩したのは、EventBusのdefaultInstanceからMainActivityへの参照が可能であるためです.後の文はこのGCチェーンのノードです:EventBusはまずDiscoverFragmentが回収できないことをもたらし、DiscoverFragmentはMainActivityの参照(framgnet.getActivity()で得られる)を保有しているため、EventBusからMainActivityまでは可能です.GCRootからMainActivityは可能なため、GCはMainActivityを回収せず、メモリが漏洩します.
解決策
EventBusの使用規範に従って、私たちは使用が終わった後、逆登録を行うべきです.FragmentのonDestroyメソッドで送信登録メソッドを呼び出し、プログラムを実行します.以前のロゴが印刷されなくなったことがわかりました.
まとめ
会社のプロジェクトに対してメモリの漏洩を調べたとき、メモリの漏洩はよく無視されていることに気づきました.そこで、最後にメモリの漏洩が発生するいくつかの状況をまとめました.1、Handlerを使用し、遅延操作を使用しました.例えば、マルチキャスト図2では、スレッドが使用される.スレッドは一般的に処理に時間がかかり、サブスレッド部分の実行時間がページのライフサイクルを検出する可能性があり、スレッドで処理しないとメモリ漏洩が発生します.解決策は,虚参照を用いてページ破棄時にスレッドを終了させるなどである.3,匿名の内部クラスを使用した.匿名内部クラスは外部クラスの参照を保持するため、ActivityまたはFragmentで匿名内部クラスを使用する場合は、内部クラスのライフサイクルが外部クラスのライフサイクルよりも大きくならないように注意してください.または静的内部クラスを使用します.4,入力パラメータに誤りがあり,プロジェクトでは友盟プッシュが使用されているため,外部に露出したAPIはUmengPushAgentというクラスが静的Contextを保有しており,Activityに入力するとメモリリークが発生する.
など、メモリ漏洩はよくありますが、LeakCanaryを使用するとシステムSDKのメモリ漏洩も検出されます.プログラムの健全かつ安定した動作のために、メモリ漏洩の問題を特定し、解決することが最適化されています.