内存泄漏分析框架LeakCanary的使用与原理解析

 在《Android性能优化(1):常见内存泄漏与优化(一)》和《Android性能优化(1):常见内存泄漏与优化(二)》文章中,我们详细剖析了垃圾回收器机制、内存泄漏的产生以及分析内存泄漏的各种工具,本文将此基础上,首先回顾一下什么是内存泄漏、Android常见的内存泄漏有哪些以及该如何优化,然后重点介绍内存泄漏分析工具LeakCanary的基本使用和实现原理。

1. 常见内存泄漏

 内存泄漏的表述可为:当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被垃圾收集器回收,结果它们就一直存在于内存中(通常指Java堆内存),占用有效空间,永远无法被删除。随着内存不断泄漏,堆中的可用空间就不断变小,这意味着为了执行常用的程序,垃圾清理需要启动的次数越来越多,非常严重的话会直接造成应用程序报OOM异常。通常,使用可达性分析算法判断对象是否死亡:

1.1 “单例模式” 造成的内存泄漏

 由于单例模式的静态特性(instance对象被static关键词修饰),Commontils对象instance的生命周期将于应用进程的一致。假如我们向CommonUtils的构造方法中传入一个Activity,后面如果这个Activity对象已经不再需要了,而Commontils对象该持有该对象的引用就会使得GC无法对其进行正常回收,从而导致了内存泄漏。优化:对于需要传入Context参数的情况,尽量使用Application的Context,因为它会伴随着应用进程的存在而存在。

/** 单例模式内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/8 17:23
 * @Description:
 */
public class CommonUtils {
    // static修饰,生命周期与Application一致
    private static CommonUtils instance;
    private Context mCtx;
	
    private CommonUtils(Context context){
        this.mCtx = context;
    }

    public static CommonUtils getInstance(Context context) {
        if(instance == null) {
            instance = new CommonUtils(context);
        }
        return instance;
    }
}

1.2 “静态实例” 造成内存泄漏

 有时为了防止一个对象的反复重建,就会使用static修饰关键字将这个对象声明为静态实例,以确保该实例始终存在的是同一个,且它的生命周期与应用相同,假设这个实例为SomeResources。然而,由于SomeResources类是一个非静态内部类,它默认持有外部类StaticInstanceActivity的引用,就会导SomeResources的对象一直持有该引用,造成内存泄漏。优化:使用单例模式实现SomeResources,或者将其改成静态内部类。如果需要传入Context参数,必须使用Application的Context。

/**非静态内部类创建静态实例造成的内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/9 10:43
 * @Description:
 */
public class StaticInstanceLeakActivity extends AppCompatActivity {
    // 静态属性,生命周期与Application一致
    private static SomeResources mSomeResources;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        if(mSomeResources == null) {
            mSomeResources = new SomeResources(this);
        }
    }

    class SomeResources {
        private Context mCtx;
        
        public SomeResources(Context context) {
            this.mCtx = context;
        }
    }
}

1.3 “Handler” 造成的内存泄漏

 由于Handler匿名内部类默认持有外部类HandlerActivity的引用,假如HandlerActivity已经不再被使用了,但是由于MessageQueue仍然有消息要处理,那么就会导致HandlerActivity对象被Handler一直持有,从而导致HandlerActivity对象无法被GC正常回收,进而造成内存泄漏。优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。如果使用持有外部类HandlerActivity对象,可以使用弱引用实现。

/** 使用Handler造成内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/8 17:55
 * @Description:
 */
public class HandlerLeakActivity extends AppCompatActivity {
	
    // 匿名内部类
    // 默认持有外部类HandlerActivity对象的引用
    private Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 处理耗时任务
                // ...
                
                mUIHandler.sendEmptyMessage(0x00);
            }
        });
    }
}

1.4 “线程” 造成的内存泄漏

 由于Java类中的非静态内部类和匿名内部类默认持有外部类的引用。对于下述示例中的MyRunnable来说,它是一个非静态内部类,将默认持有ThreadActivity对象的引用。假如子线程的任务在ThreadActivity销毁之前还未完成,就会导致ThreadActivity无法被GC正常回收,造成内存泄漏。优化:将MyRunnable独立出来或使用静态内部类(static关键字修饰类),因为静态内部类不持有外部类的引用。

/** 使用线程造成的内存泄漏案例
 * @Auther: Jiangdg
 * @Date: 2019/10/9 10:04
 * @Description:
 */
public class ThreadLeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 开启一个子线程
        new Thread(new MyRunnable()).start();
    }

    class MyRunnable implements Runnable {

        @Override
        public void run() {
			// 执行耗时任务
            Thread.sleep(100000);
        }
    }
}

1.5 “使用WebView” 造成的内存泄漏

 使用WebView造成泄漏的原因是在不使用WebView时没有调用其destory方法来销毁它,导致其长期占用内存且不能被回收。在优化时,可以为WebView开启另外一个进程,通过AIDL与主线程进行通信,便于WebVIew所在的进程可以根据业务需要选择合适的时机进行销毁。

/** 使用WebView造成的内存泄漏案例与优化
 * @Auther: Jiangdg
 * @Date: 2019/12/26 10:04
 * @Description:
 */
public class WebViewLeakActivity extends AppCompatActivity {
    private WebView mWebView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mWebView = (WebView)findViewById(R.id.webview);
        mWebView.loadUrl("https://blog.csdn.net/andrexpert");
    }

     @Override
    protected void onDestroy() {
        super.onDestroy();
        // 优化
        destoryWebView();
        Process.killProcess(Process.myPid());
    }
    
    private void destoryWebView() {
        if(mWebView != null) {
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destory();
        }
    }
}

2. LeakCanary使用与原理解析

LeakCanarysquare开源的一个检测内存泄漏的工具,它使用非常简单,主要用来检测Activity和Fragment内存泄漏,如果发生内存泄漏,直接在用UI显示哪里发生了泄漏并展示对象引用链。LeakCanary效果如下:
在这里插入图片描述

2.1 LeakCanary使用方法

(1) 添加app.gradle依赖 :

  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'

(2)初始化LeakCanary,在Application中添加:

public class ExampleApplication extends Application {

    @Override public void onCreate() {
        super.onCreate();
        // 判断当前进程是否为LeakCanary进程,该进程运行一个HeapAnalyzerService服务
        // 如果不是,则初始化LeakCanary进程
        if (! LeakCanary.isInAnalyzerProcess(this)) {
            LeakCanary.install(this);
        }
        
        // Normal app init code...
    }
}

 注意:除了activities和fragments外,LeakCanary支持监听应用中的任何对象,假如这个对象不再使用到的话,通过执行下列代码实现对某个对象的监听。代码如下:

RefWatcher.watch(myDetachedView)

 LeakCanary使用非常简单,完成上述两部的配置后,当我们在debug模式下构建、运行app时,LeakCanary就能检测出Activity或Fragment是否出现了内存泄漏,并以通知的形式给出监测结果。另外,需要注意的是,目前LeakCanary最新版为2.0,这个版本是纯Kotlin开发,考虑到大众性,这里选取1.6.3版本,也就是最后一次用Java开发的版本,我们接下来的源码分析也是基于此版本进行的。

2.2 LeakCanary原理解析

 LeakCanary的入口方法是LeanCanary$install方法,该方法源码如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application)   // 创建一个AndroidRefWatcherBuilder对象
               .listenerServiceClass(DisplayLeakService.class) // 注释1
               .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 注释2
               .buildAndInstall(); // 注释2
}

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
}

 install方法目的就是创建并返回一个RefWatcher对象,这个RefWatcher是LeakCanary的核心类,通过建造者模式构建。其中,listenerServiceClass方法传入了展示分析结果的Service(DisplayLeakService);excludedRefs方法排除开发中可以忽略的泄漏路径;buildAndInstall是主要函数,实现对activity的释放监听。接下来,我们直接看buildAndInstall方法源码:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// AndroidRefWatcherBuilder.java
public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
        throw new UnsupportedOperationException("buildAndInstall() should 
                                                + "only be called once.");
    }
    // 构建一个RefWacher对象
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
        if (enableDisplayLeakActivity) {
            LeakCanaryInternals.setEnabledAsync(context, 
                                                DisplayLeakActivity.class, true);
        }
        // 监听所有Activities
        if (watchActivities) {
            ActivityRefWatcher.install(context, refWatcher);
        }
        // 监听所有fragments
        if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
        }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
}

 从上述源码可知,它首先会调用AndroidRefWatcherBuilder的build方法构建一个RefWatcher实例,然后分别调用ActivityRefWatcher.install方法和FragmentRefWatcher.Helper.install方法实现对所有activities和fragments的释放监听。下面我们就以分析如何监听activity为例:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    // 获取应用的application
    Application application = (Application) context.getApplicationContext();
    // 实例化一个ActivityRefWatcher对象
    ActivityRefWatcher activityRefWatcher = 
        				new ActivityRefWatcher(application, refWatcher);
    // 调用registerActivityLifecycleCallbacks来监听Activity的生命周期
    application.registerActivityLifecycleCallbacks(activityRefWatcher.
                                                 lifecycleCallbacks);
}

 从install方法源码可以看出,LeakCanary主要通过调用Application的registerActivityLifecycleCallbacks方法实现对activity释放(销毁)监听,该方法主要用来统一管理所有activity的生命周期。所有Activity在销毁时都会回调ActivityLifecycleCallbacks的onActivityDestroyed方法,也就是说,LeakCanary是在Activity的onDestory方法中实施监听的,通过调用RefWatcher.watch方法实现。源码如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
    @Override public void onActivityDestroyed(Activity activity) {
        // 在Activity销毁时,监控当前activity
        // 传入的是activity的引用
        refWatcher.watch(activity);
    }
};

 接下来,我们来看LeakCanary是如何监听activity是否发生泄漏。RefWatcher.watch源码:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
public void watch(Object watchedReference) {
    // watchedReference为被监视的activity引用
    watch(watchedReference, "");
}

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
        return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    // 自动生成一个主键,作为全局唯一标识符
    // 并插入到retainedKeys集合中
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    // 1. 将activity的引用包装到KeyedWeakReference中
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 2. 检测是否发生泄漏
    ensureGoneAsync(watchStartNanoTime, reference);
}

 在RefWatcher.watch方法中,完成以下两件事情:

首先,将当前被监控的activity引用、自动生成的key和一个ReferenceQueue 对象包装到一个KeyedWeakReference对象中,该对象继承于WeakReference(弱引用)。监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果能够被回收,则说明引用可达,垃圾回收器就会将该WeakReference引用(包含被监控的activity)放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收。KeyedWeakReference类源码如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \KeyedWeakReference.java

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
private final ReferenceQueue<Object> queue;
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(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, 
                                                               "referenceQueue"));
        this.key = checkNotNull(key, "key");
        this.name = checkNotNull(name, "name");
    }
}

其次,检测当前被监控的activity是否发生了泄漏,通过调用RefWatcher#ensureGoneAsync方法实现,该方法又调用了RefWatcher#ensureGone。相关源码如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
        @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
        }
    });
}

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 1. 确定是否存在内存泄漏
    // (1)判断当前activity的引用是否存在ReferenceQueue中,
    //  如果存在,则说明引用可达,能够被GC回收,同时将其key从retainedKeys集合中删除
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
        // The debugger can create false leaks.
        return RETRY;
    }
    // (2)确定retainedKeys集合中是否存在该activity对应的key
    // 如果不存在了,说明该对象已经被回收,直接返回
    if (gone(reference)) {
        return DONE;
    }
    // (3)如果存在,先触发一下GC操作,再尝试判断该activity的对象引用
    // 是否保存到了ReferenceQueue
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    
    // 2. 再次确定retainedKeys集合中是否存在该activity对应的key
    // 如果仍然存在,则说明发生了内存泄漏.生成堆内存快照,分析快照
    if (!gone(reference)) {
        long startDumpHeap = System.nanoTime();
        long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
        // (1) 创建heapDump文件,还没写入
        File heapDumpFile = heapDumper.dumpHeap();
        if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
        }
        long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() 
                                                       - startDumpHeap);
		//(2)创建HeapDump对象
        HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
            .referenceKey(reference.key)
            .referenceName(reference.name)
            .watchDurationMs(watchDurationMs)
            .gcDurationMs(gcDurationMs)
            .heapDumpDurationMs(heapDumpDurationMs)
            .build();
        //(3)调用heapdumpListener分析
        // 调用HeapAnalyzerService的analyze实现,即后台执行分析任务
        heapdumpListener.analyze(heapDump);
    }
    return DONE;
}

 从ensureGone方法源码可知,它首先会去确定被监控的activity对象是否发生了内存泄漏,然后如果确定了确实发生了泄漏,就会dump内存快照并对快照进行分析,最终得到泄漏的具体信息。接下来,我们将对这三个方面进行详细分析:

(1)确定是否存在内存泄漏

 确定被监控的activity对象是否存在内存泄漏,主要是通过调用removeWeaklyReachableReferencesgone方法实现的,具体策略为:首先,调用removeWeaklyReachableReferences判断当前被监控的activity对象的引用是否存在ReferenceQueue中,如果存在说明,该activity对象引用可达,能够被GC回收,此时就将其key从retainedKeys这个Set集合中移除;然后,调用gone方法确定当前被监控的activity对象的引用是否存在retainedKeys集合,如果不存在,说明该activity对象已经被回收,直接返回。但是,如果仍然存在,为了确定GC延迟或误判,手动触发一下GC操作,然后再进行一次上面的判定操作,如果Gone方法仍然返回false,则说明被监控的activity对象发生了内存泄漏。相关源码如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private boolean gone(KeyedWeakReference reference) {
    // 判定被监控的activity对象对应的key
    // 是否存在于retainedKeys集合中
    // 如果不存在,说明该对象已经被GC回收,不存在内存泄漏
    return !retainedKeys.contains(reference.key);
}

private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
	// 判定被监控的activity引用是否保存在ReferenceQueue中
    // 如果存在,则将其对应的key从retainedKeys集合中移除
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
    }
}

(2)将堆内存转储到文件并分析,获取泄漏对象的GC最短强引用路径

 前面说到,如果Gone方法返回false说明被监控的activity对象发生了内存泄漏,接下来,将进入内存泄漏分析过程。具体策略为:首先,创建一个heapDump文件,此时还没有写入;然后,将堆内存信息转储到该heapDump文件,并创建一个HeapDump对象;最后,调用heapdumpListener的analyze方法进入分析流程。需要注意的是,heapdumpListener由RefWatch构造方法传入,前面说到RefWatch对象是通过建造者模式的形式创建的,因此,我们找到了AndroidRefWatcherBuilder,该类包含一个defaultHeapDumpListener方法即可说明heapdumpListener(类型为HeapDump.Listener)的实例化过程,即实现类为ServiceHeapDumpListener。也就是说,heapdumpListener.analyze为调用ServiceHeapDumpListener#analyze方法,该方法中最终调用的是HeapAnalyzerService#runAnalysis方法在后台服务中执行堆快照分析任务。相关源码如下:

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \AndroidRefWatcherBuilder.java
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}

// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ServiceHeapDumpListener.java
public final class ServiceHeapDumpListener implements HeapDump.Listener {

    private final Context context;
    private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

    public ServiceHeapDumpListener(@NonNull final Context context,
                                   @NonNull final Class<? extends 
                        AbstractAnalysisResultService> listenerServiceClass) {
        this.listenerServiceClass = checkNotNull(listenerServiceClass, 
                                                 "listenerServiceClass");
        this.context = checkNotNull(context, "context").getApplicationContext();
    }

    @Override public void analyze(@NonNull HeapDump heapDump) {
        checkNotNull(heapDump, "heapDump");
        // 后台执行分析任务
        HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
    }
}

 接下来,我们分析HeapAnalyzerService#runAnalysis方法,它的源码如下:

public final class HeapAnalyzerService extends ForegroundService
    implements AnalyzerProgressListener {
    ...
    public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    	setEnabledBlocking(context, HeapAnalyzerService.class, true);
    	setEnabledBlocking(context, listenerServiceClass, true);
        // listenerServiceClass
	    // 负责记录日志和展示通知
    	Intent intent = new Intent(context, HeapAnalyzerService.class);
    	intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    	intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    	// 启动自身,并将其置为前台
    	ContextCompat.startForegroundService(context, intent);
  }
}

 从HeapAnalyzerService源码可知,它是一个IntentServcie,实际上就是一个Service,只是与普通Service不同的是,它被启动后会在子线程执行具体的任务,即调用onHandleIntentInForeground方法执行任务,当任务执行完毕后,该Service会被自动销毁。在HeapAnalyzerService#runAnalysis方法中,就是启动该IntentService,并将其置为前台服务,以降低被系统杀死的概率。现在,我们就看下HeapAnalyzerService#onHandleIntentInForeground方法做了什么,源码如下:

protected void onHandleIntentInForeground(@Nullable Intent intent) {
    // DisplayLeakService.class
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA); 
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
    // 创建HeapAnalyzer
    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, 
                         heapDump.reachabilityInspectorClasses);
    // HeapAnanlyzer工具分析
    // 即分析堆内存快照,找出 GC roots 的最短强引用路径,并确定是否是泄露
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, 
                                                      heapDump.referenceKey,
                                                   heapDump.computeRetainedHeapSize);
    // 启动DisplayLeakService记录日志和展示通知
    AbstractAnalysisResultService.sendResultToListener(this, 
                                                       listenerClassName, 
                                                       heapDump, result);
}

 从该方法源码可知,它主要是创建一个HeapAnalyzer对象,并调用该对象的checkForLeak进行分析,然后将得到的结果交给DisplayLeakService进行通知展示。这里,我们分析下HeapAnalyzer#checkForLeak方法分析堆内存快照文件的流程,该方法源码如下:

public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull String referenceKey,
      boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();
	// 确定堆快照文件是否存在
    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " 
                                                         + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
	  // 将heap文件封装成MemoryMappedFileBuffer
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
	  // 创建hprof解析器,解析hprof文件
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
	  // 移除相同GC root
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
	  // 找出泄漏的对象
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // False alarm, weak reference was cleared in between key check and heap dump.
      //检测是否存在泄漏的引用
      if (leakingRef == null) {
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
	   //根据leakingRef寻找引用路径
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, 
                           computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

 由上述源码可知,该方法最终调用findLeakingReference方法来判断是否真的存在内存泄漏,如果存在(leakingRef!=null),就调用findLeakTrace方法找出这个泄漏对象的GC Root最短强引用路径。由于这部分不是我关注的重点,就不继续分析下去了,感兴趣的朋友可以继续往下面分析。LeakCanary时序图如下:
在这里插入图片描述
 至此,LeakCanary框架实现原理分析完毕,最后我们再总结一下:LeakCanary是通过在Application的registerActivityLifecycleCallbacks方法实现对Activity销毁监听的,该方法主要用来统一管理所有activity的生命周期。所有Activity在销毁时在其OnDestory方法中都会回调ActivityLifecycleCallbacks#onActivityDestroyed方法,而LeakCanary要做的就是在该方法中调用RefWatcher#watch方法实现对activity进行内存泄漏监控。那么,LeakCanary是如何判断某个Activity可能会发生内存泄漏呢?答案是:WeakReference和ReferenceQueue,即LeakCanary利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果能够被回收,则说明引用可达,垃圾回收器就会将该WeakReference引用存放到ReferenceQueue中。假如我们要监视某个activity对象,LeakCanary就会去ReferenceQueue找这个对象的引用,如果找到了,说明该对象是引用可达的,能被GC回收,如果没有找到,说明该对象有可能发生了内存泄漏。最后,LeakCanary会将Java堆转储到一个.hprof文件中,再使用Shark(堆分析工具)分析.hprof文件并定位堆转储中“滞留”的对象,并对每个"滞留"的对象找出 GC roots 的最短强引用路径,并确定是否是泄露,如果泄漏,建立导致泄露的引用链。最后,再将分析完毕的结果以通知的形式展现出来。

发布了83 篇原创文章 · 获赞 293 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/103781575