从源码上剖析Android内存泄露工具LeakCanary

内存泄露在开发过程中我们会经常遇到,分析内存泄露的工具经常会用到Heap Tool 与 Memory Analyzer tool(MAT)。Heap Tool可以查看当前的内存快照,从数据里可以看到当前内存的占用和回收情况,每次垃圾回收这里的数据都会更新,因为会不断获取内存数据刷新显示,所以这时候对应用操作会出现卡顿。 Heap Tool提供的是一个内存的总体情况,图表显示的内容比较简单,如果要具体分析的话最好生成.hprof文件,使用MAT工具进行分析。一般用到MAT工具分析内存都是因为应用发生了内存泄露,需要自己去分析可能泄露的地方,然后用MAT工具去验证。过程比较复杂,而最近Square公司开源了一个内存泄露检测项目LeakCanary,极大地简化了这个过程,可以说是Android内存泄露检测的终极利器。这里主要讲怎样把LeakCanary这个工具集成到我们的APP应用中。

1、LeakCanary的简介

LeakCanary是一个检测内存泄露的开源类库, 它能十分方便的检测出项目中内存泄露,同时提供非常友好的通知提示,LOG信息详细。最主要的是它可以保存映像文件。可以在debug包中轻松检测内存泄露。通过简单粗暴的方式来让开发者获取自己应用的内存泄露情况,而且得益于gradle强大的可配置性,可以确保只在编译debug版本时才会检查内存泄露,而在release等版本的时候则会自动跳过检查,避免影响性能。LeakCanary会检测应用的内存回收情况,如果发现有垃圾对象没有被回收,就会去分析当前的内存快照,也就是上边MAT用到的.hprof文件,找到对象的引用链,并显示在页面上。跟MAT相比,LeakCanary具有一下几个优点:
(1)使用超级简单
(2)效果十分明显
(3)提供LOG信息

2、排查内存泄露的步骤

(1)通过统计平台了解OOM情况
(2)重现问题
(3)在发生内存泄露时Dump内存
(4)在分析工具中反复查看,找到原本该被回收掉的对象
(5)计算此对象到GC roots的最短强引用路径
(6)确定并修复问题

3、LeakCanary的一般用法

(1)监控Activity泄露 Activity 当作为 Context 对象使用,在不同场合由各种对象引用 Activity。所以,Activity 泄漏是一个重要的需要检查的内存泄漏之一。

public class AppTest extends Application {

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

    private RefWatcher refWatcher;

    @Override public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
    }
}

LeakCanary.install()返回一个配置好了的 RefWatcher 实例。它同时安装了ActivityRefWatcher来监控 Activity 泄漏。即当 Activity.onDestroy()被调用之后,如果这个 Activity 没有被销毁,logcat 就会打印出信息告诉你内存泄漏发生了。
(2)监控Fragment泄露

public abstract class FragmentTest extends Fragment {

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

当 Fragment.onDestroy() 被调用之后,如果这个 fragment 实例没有被销毁,那么就会从 logcat 里看到相应的泄漏信息。
(3)监控其他泄露

    RefWatcher refWatcher = AppTest.getRefWatcher(getActivity());
    refWatcher.watch(someObjNeedGced);

当someObjNeedGced 还在内存中时,就会在 logcat 里看到内存泄漏的提示。

4、在自己的APP应用中怎样使用LeakCanary检测工具

(1)添加依赖
在build.gradle文件中添加

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

这两个依赖的区别:
主要是为了在debug版本和release版本上实现不同的行为,比如在debug 版本上可以提示leakcanary的信息和查看leakcanary的log信息,但是在release版本上我们是不希望用户看到这些信息的,而为了不改写代码,所以使用了这种方式。

(2)添加一个Application类
新建一个类APP extends Application类

public class AppTest extends Application {

    private RefWatcher mRefWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        mRefWatcher = LeakCanary.install(this);
        Log.i("TAG"," mRefWatcher"+ mRefWatcher);
    }
}

5、LeakCanary工作原理

这里写图片描述

过程分析:

(1)RefWatcher.watch() 会以监控对象来创建一个 KeyedWeakReference 弱引用对象。
(2)在 AndroidWatchExecutor 的后台线程里,来检查弱引用已经被清除了,如果没被清除,则执行一次 GC。
(3)如果弱引用对象仍然没有被清除,说明内存泄漏了,系统就导出 hprof 文件,保存在 app 的文件系统目录下。
(4)HeapAnalyzerService 启动一个单独的进程,使用 HeapAnalyzer 来分析 hprof 文件。它使用另外一个开源库 HAHA。。
(5)HeapAnalyzer 通过查找 KeyedWeakReference 弱引用对象来查找内存泄漏。
(6)HeapAnalyzer 计算 KeyedWeakReference 所引用对象的最短强引用路径,来分析内存泄漏,并且构建出对象引用链出来。
(7) 内存泄漏信息送回给 DisplayLeakService,它是运行在 app 进程里的一个服务。然后在设备通知栏显示内存泄漏信息。

怎么知道某个变量已被GC回收:

RefWatcher.java的ensureGone()方法,最主要是利用WeakRerence 和ReferenceQueue机制。就是当弱引用WeakRerence所引用的对象被回收后,这个WeakRerence对象就会被添加到 ReferenceQueue队列里,然后就可以通过其 poll()方法获取到这个被回收的对象的 WeakRerence实例,进而知道需要监控的对象是否被回收了。

源码:

LeakCanary.java

public final class LeakCanary {
    public static RefWatcher install(Application application) {
        return install(application, DisplayLeakService.class);
    }

    public static RefWatcher install(Application application, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        if(isInAnalyzerProcess(application)) {
            return RefWatcher.DISABLED;
        } else {
            enableDisplayLeakActivity(application);
            ServiceHeapDumpListener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass);
            RefWatcher refWatcher = androidWatcher(application, heapDumpListener);
            ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
            return refWatcher;
        }
    }

    public static RefWatcher androidWatcher(Application app, Listener heapDumpListener) {
        AndroidDebuggerControl debuggerControl = new AndroidDebuggerControl();
        AndroidHeapDumper heapDumper = new AndroidHeapDumper(app);
        heapDumper.cleanup();
        return new RefWatcher(new AndroidWatchExecutor(), debuggerControl, GcTrigger.DEFAULT, heapDumper, heapDumpListener);
    }


    public static boolean isInAnalyzerProcess(Context context) {
        return isInServiceProcess(context, HeapAnalyzerService.class);
    }

}

RefWatcher.java

public RefWatcher(Executor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger, HeapDumper heapDumper, Listener heapdumpListener) {
        this.watchExecutor = (Executor)Preconditions.checkNotNull(watchExecutor, "watchExecutor");
        this.debuggerControl = (DebuggerControl)Preconditions.checkNotNull(debuggerControl, "debuggerControl");
        this.gcTrigger = (GcTrigger)Preconditions.checkNotNull(gcTrigger, "gcTrigger");
        this.heapDumper = (HeapDumper)Preconditions.checkNotNull(heapDumper, "heapDumper");
        this.heapdumpListener = (Listener)Preconditions.checkNotNull(heapdumpListener, "heapdumpListener");
        this.retainedKeys = new CopyOnWriteArraySet();
        this.queue = new ReferenceQueue();
    }

    public void watch(Object watchedReference) {
        this.watch(watchedReference, "");
    }

    public void watch(Object watchedReference, String referenceName) {
        Preconditions.checkNotNull(watchedReference, "watchedReference");
        Preconditions.checkNotNull(referenceName, "referenceName");
        if(!this.debuggerControl.isDebuggerAttached()) {
            final long watchStartNanoTime = System.nanoTime();
            String key = UUID.randomUUID().toString();
            this.retainedKeys.add(key);
            final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
            this.watchExecutor.execute(new Runnable() {
                public void run() {
                    RefWatcher.this.ensureGone(reference, watchStartNanoTime);
                }
            });
        }
    }

    void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        this.removeWeaklyReachableReferences();
        if(!this.gone(reference) && !this.debuggerControl.isDebuggerAttached()) {
            this.gcTrigger.runGc();
            this.removeWeaklyReachableReferences();
            if(!this.gone(reference)) {
                long startDumpHeap = System.nanoTime();
                long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
                File heapDumpFile = this.heapDumper.dumpHeap();
                if(heapDumpFile == null) {
                    return;
                }

                long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
                this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, watchDurationMs, gcDurationMs, heapDumpDurationMs));
            }

        }
    }

    private boolean gone(KeyedWeakReference reference) {
        return !this.retainedKeys.contains(reference.key);
    }

    private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
            this.retainedKeys.remove(ref.key);
        }

    }

    static {
        DISABLED = new RefWatcher(new Executor() {
            public void execute(Runnable command) {
            }
        }, new DebuggerControl() {
            public boolean isDebuggerAttached() {
                return true;
            }
        }, GcTrigger.DEFAULT, new HeapDumper() {
            public File dumpHeap() {
                return null;
            }
        }, new Listener() {
            public void analyze(HeapDump heapDump) {
            }
        });
    }
}

KeyedWeakReference.java

final class KeyedWeakReference extends WeakReference<Object> {
    public final String key;
    public final String name;

    KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) {
        super(Preconditions.checkNotNull(referent, "referent"), (ReferenceQueue)Preconditions.checkNotNull(referenceQueue, "referenceQueue"));
        this.key = (String)Preconditions.checkNotNull(key, "key");
        this.name = (String)Preconditions.checkNotNull(name, "name");
    }
}

HeapAnalyzerService.java

public final class HeapAnalyzerService extends IntentService {
    private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
    private static final String HEAPDUMP_EXTRA = "heapdump_extra";
    private final HeapAnalyzer heapAnalyzer = new HeapAnalyzer(AndroidExcludedRefs.createAndroidDefaults(), AndroidExcludedRefs.createAppDefaults());

    public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        Intent intent = new Intent(context, HeapAnalyzerService.class);
        intent.putExtra("listener_class_extra", listenerServiceClass.getName());
        intent.putExtra("heapdump_extra", heapDump);
        context.startService(intent);
    }

    public HeapAnalyzerService() {
        super(HeapAnalyzerService.class.getSimpleName());
    }

    protected void onHandleIntent(Intent intent) {
        String listenerClassName = intent.getStringExtra("listener_class_extra");
        HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
        AnalysisResult result = this.heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    }
}

AndroidHeapDumper.java

public final class AndroidHeapDumper implements HeapDumper {
    private final File heapDumpFile;

    public AndroidHeapDumper(Context context) {
        this.heapDumpFile = new File(context.getFilesDir(), "suspected_leak_heapdump.hprof");
    }

    public File dumpHeap() {
        if(this.heapDumpFile.exists()) {
            Log.d("AndroidHeapDumper", "Could not dump heap, previous analysis still is in progress.");
            return null;
        } else {
            try {
                Debug.dumpHprofData(this.heapDumpFile.getAbsolutePath());
                return this.heapDumpFile;
            } catch (IOException var2) {
                this.cleanup();
                Log.e("AndroidHeapDumper", "Could not perform heap dump", var2);
                return null;
            }
        }
    }

    public void cleanup() {
        if(this.heapDumpFile.exists()) {
            Log.d("AndroidHeapDumper", "Previous analysis did not complete correctly, cleaning: " + this.heapDumpFile);
            this.heapDumpFile.delete();
        }

    }
}

ServiceHeapDumpListener.java

public final class ServiceHeapDumpListener implements Listener {
    private final Context context;
    private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

    public ServiceHeapDumpListener(Context context, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        LeakCanary.setEnabled(context, listenerServiceClass, true);
        LeakCanary.setEnabled(context, HeapAnalyzerService.class, true);
        this.listenerServiceClass = (Class)Preconditions.checkNotNull(listenerServiceClass, "listenerServiceClass");
        this.context = ((Context)Preconditions.checkNotNull(context, "context")).getApplicationContext();
    }

    public void analyze(HeapDump heapDump) {
        Preconditions.checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
    }
}

HeapDump.java

public final class HeapDump implements Serializable {
    public final File heapDumpFile;
    public final String referenceKey;
    public final String referenceName;
    public final long watchDurationMs;
    public final long gcDurationMs;
    public final long heapDumpDurationMs;

    public HeapDump(File heapDumpFile, String referenceKey, String referenceName, long watchDurationMs, long gcDurationMs, long heapDumpDurationMs) {
        this.heapDumpFile = (File)Preconditions.checkNotNull(heapDumpFile, "heapDumpFile");
        this.referenceKey = (String)Preconditions.checkNotNull(referenceKey, "referenceKey");
        this.referenceName = (String)Preconditions.checkNotNull(referenceName, "referenceName");
        this.watchDurationMs = watchDurationMs;
        this.gcDurationMs = gcDurationMs;
        this.heapDumpDurationMs = heapDumpDurationMs;
    }

    public HeapDump renameFile(File newFile) {
        this.heapDumpFile.renameTo(newFile);
        return new HeapDump(newFile, this.referenceKey, this.referenceName, this.watchDurationMs, this.gcDurationMs, this.heapDumpDurationMs);
    }

    public interface Listener {
        void analyze(HeapDump var1);
    }
}

6、Demo展示

(1)添加依赖

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}

(2)新建Application类

public class AppTest extends Application {
    private RefWatcher mRefWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        mRefWatcher = LeakCanary.install(this);
        mRefWatcher.watch(this);
        Log.i("TAG"," mRefWatcher"+ mRefWatcher);
    }
}

(3)AndroidManifest.xml配置

<application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:name=".AppTest"
        android:theme="@style/AppTheme" >

(4)MainActivity.class实现内存泄漏方法

public class MainActivity extends Activity {
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button)this.findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                start();
            }
        });
    }

    public void start(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(20000);

            }
        }).start();
    }
}

start()方法体是一个匿名内部类,因此它隐式的持有一个外部类的对象,也就是MainActivity,如果MainActivity在Thread执行完成前就销毁了,这个activity实例就发生泄漏。
这里写图片描述
Log信息:

D/LeakCanary(17016): In com.meizu.leakcanarydemo:1.0:1.
D/LeakCanary(17016): * com.meizu.leakcanarydemo.AppTest has leaked:
D/LeakCanary(17016): * GC ROOT android.app.ActivityThread.mInitialApplication
D/LeakCanary(17016): * leaks com.meizu.leakcanarydemo.AppTest instance
D/LeakCanary(17016): 
D/LeakCanary(17016): * Reference Key: ad6a4265-721e-40fb-b9cd-8dd95506c5fd
D/LeakCanary(17016): * Device: Meizu Meizu MX5 MX5
D/LeakCanary(17016): * Android Version: 5.1 API: 22
D/LeakCanary(17016): * Durations: watch=5229ms, gc=153ms, heap dump=1197ms, analysis=14279ms
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值