美文网首页
内存泄露 && LeakCanary

内存泄露 && LeakCanary

作者: 卡路fly | 来源:发表于2020-05-20 14:34 被阅读0次

内存泄漏(Memory Leak)

程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

内存溢出:

应用系统中存在无法回收或使用的内存过多,最终
使得程序运行要用到的内存大于能提供的最大内存。

  • StackOverflow:因为递归太深发生堆栈溢出
  • OutOfMemory:内存占有量超过了虚拟机分配的最大值
    OOM出现情况:
    • 加载图片过多过大
    • 分配特大数组
    • 内存资源过多来不及释放等

Android常见内存泄露

1、静态成员变量持有外部(短周期临时)对象引用。 如单例类(类内部静态属性)持有一个activity(或其他短周期对象)引用时,导致被持有的对象内存无法释放。
2、内部类。当内部类与外部类生命周期不一致时,就会造成内存泄漏。如非静态内部类创建静态实例、Activity中的Handler或Thread等。
3、资源没有及时关闭。如数据库、IO流、Bitmap、注册的相关服务、webview、动画等。
4、集合内部Item没有置空。
5、方法块内不使用的对象,没有及时置空。


LeakCanary

LeakCanary提供了一种很方便的方式,让我们在开发阶段测试内存泄露,我们不需要自己根据内存块来分析内存泄露的原因,我们只需要在项目中集成他,然后他就会帮我们检测内存泄露,并给出内存泄露的引用链

集成&基本使用

  • 在gradle中添加依赖,这种不区分debug和release
    implementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
  • Application 的onCreate方法install,生成mRefWatcher
public class App extends Application {

    private RefWatcher mRefWatcher;

    @Override
    public void onCreate() {
        super.onCreate();

        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }

        mRefWatcher = LeakCanary.install(this);

    }


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

  • Fragment监听(refWatcher.watch(this);)
public abstract class BaseFragment extends Fragment {
 
  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = App.getRefWatcher(getActivity());
    refWatcher.watch(this);
  }
}

基本原理

  1. 主要在Activity的onDestroy方法中,手动调用 GC
  2. 利用ReferenceQueue+WeakReference,来判断是否有释放不掉的引用
  3. 结合dump memory的hprof(堆存储文件)文件, 用HaHa分析出泄漏位置

源码分析

  • LeakCanary会单独开一进程,用来执行分析任务,和监听任务分开处理。

  • 方法install中主要是构造来一个RefWatcher,用于监控、追踪应用中的对象引用。

    监听原理在于 Application 的
    registerActivityLifecycleCallbacks方法,可以对应用内所有 Activity 的生命周期做监听, LeakCanary只监听了
    Destroy方法。

这里分析对象是否内存泄露的是RefWatcher类,下面简单介绍一下这个类的成员变量

  • WatchExecutor watchExecutor:确保任务在主线程进行,同时默认延迟5s执行任务,留时间给系统GC
    -DebuggerControl debuggerControl:控制中心
    -GcTrigger gcTrigger:内部调用Runtime.getRuntime().gc(),手动触发GC
    -HeapDumper heapDumper:用于创建.hprof文件,用于储存head堆快照,可以知道哪些程序在大部分使用内存
    -HeapDump.Listener heapdumpListener:分析结果完成后会告诉这个监听器
    -ExcludedRefs excludedRefs:分析内存泄露的白名单
  • Activity的OnDestroy() 中回调refWatcher.watch()
  • KeyedWeakReference是WeakReference类的子类,用了 KeyedWeakReference(referent, key, name, ReferenceQueue )的构造方法,将监听的对象(activity)引用传递进来,并且New出一个ReferenceQueue来监听GC后 的回收情况。
  • 方法ensureGone中通过检测referenceQueue队列中的引用情况,来判断回收情况,通过手动GC来进一步确认回收情况。整个过程肯定是个耗时卡UI的,整个过程会在WatchExecutor中执行

用watchExecutor去调度分析任务,这个主要是保证,在主线程进行,延迟5s,让系统有时间GC

  • LeakCanary已经利用Looper机制做了一定优化,利用主线程空闲的时候执行检测任务,WatchExecutor中Looper中的MessageQueue有个mIdleHandlers队列,在获取下个要执行的Message时,如果没有发现可执行的下个Msg,就会回调queueIdle()方法。
  • 首先通过removeWeaklyReachableReferences()方法,尝试从弱引用队列获取待分析对象,如果不为空说明被系统回收了,就把retainedKeys中的key值移除,如果被系统回收直接返回DONE,如果没有被系统回收,就手动调用 gcTrigger.runGc();手动触发系统gc,然后再次调用removeWeaklyReachableReferences()方法,如过还是为空,则判断为内存泄露

  • 手动触发GC后,调用enqueueReferences方法沉睡100ms,给系统GC时间, System.runFinalization();,这个是强制调用已失去引用对象的finalize方法

  • 确定有内存泄漏后,调用heapDumper.dumpHeap();生成.hprof文件,然后回调到heapdumpListener监听,这个监听实现是ServiceHeapDumpListener类,会调analyze()方法

  • HeapDump是一个modle类,里面用于储存一些分析类强引用的需要信息 HeapAnalyzerService.runAnalysis方法是发送了一个intent,启动了HeapAnalyzerService服务,这是一个intentService

  • 启动服务后,会在onHandleIntent方法启动分析,找到内存泄露的引用关系

找到引用关系

  • 启动分析内存泄露的服务后,会实例化一个HeapAnalyzer对象,然后调用checkForLeak方法来分析最终得到的结果,
  • checkForLeak这里用到了Square的另一个库haha,哈哈哈哈哈,名字真的就是叫这个,开源地址:github.com/square/haha…
  • 得到结果后调用AbstractAnalysisResultService.sendResultToListener()方法,这个方法启动了另一个服务

  public static void sendResultToListener(Context context, String listenerServiceClassName,
      HeapDump heapDump, AnalysisResult result) {
    Class<?> listenerServiceClass;
    try {
      listenerServiceClass = Class.forName(listenerServiceClassName);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    Intent intent = new Intent(context, listenerServiceClass);
    intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
    intent.putExtra(RESULT_EXTRA, result);
    context.startService(intent);
  }

listenerServiceClassName就是开始LeakCanary.install方法传入的DisplayLeakService,它本身也是一个intentService

@Override 
protected final void onHandleIntent(Intent intent) {
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
    try {
      onHeapAnalyzed(heapDump, result);
    } finally {
      //noinspection ResultOfMethodCallIgnored
      heapDump.heapDumpFile.delete();
    }
  }

然后调用自身的onHeapAnalyzed方法

protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", new Object[]{leakInfo});
    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if(shouldSaveResult) {
        heapDump = this.renameHeapdump(heapDump);
        resultSaved = this.saveResult(heapDump, result);
    }

    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;

    // 设置消息通知的 pendingIntent/contentTitle/contentText
    ...

    int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
    LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
    this.afterDefaultHandling(heapDump, result, leakInfo);
}

这个方法首先判断是否需要把信息存到本地,如果需要就存到本地,然后设置消息通知的基本信息,最后通过LeakCanaryInternals.showNotification方法调用系统的系统通知栏,告诉用户有内存泄露

总结

1,用ActivityLifecycleCallbacks接口来检测Activity生命周期
2,WeakReference + ReferenceQueue来监听对象回收情况
3,Apolication中可通过processName判断是否是任务执行进程
4,MessageQueue中加入一个IdleHandler来得到主线程空闲回调


相关文章

网友评论

      本文标题:内存泄露 && LeakCanary

      本文链接:https://www.haomeiwen.com/subject/chvgohtx.html