内存泄露 && LeakCanary

2020-05-20  本文已影响0人  卡路fly

内存泄漏(Memory Leak)

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

内存溢出:

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

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

Android常见内存泄露

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


LeakCanary

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

集成&基本使用

    implementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
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;
    }
}

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分析出泄漏位置

源码分析

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

  • WatchExecutor watchExecutor:确保任务在主线程进行,同时默认延迟5s执行任务,留时间给系统GC
    -DebuggerControl debuggerControl:控制中心
    -GcTrigger gcTrigger:内部调用Runtime.getRuntime().gc(),手动触发GC
    -HeapDumper heapDumper:用于创建.hprof文件,用于储存head堆快照,可以知道哪些程序在大部分使用内存
    -HeapDump.Listener heapdumpListener:分析结果完成后会告诉这个监听器
    -ExcludedRefs excludedRefs:分析内存泄露的白名单

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

  • LeakCanary已经利用Looper机制做了一定优化,利用主线程空闲的时候执行检测任务,WatchExecutor中Looper中的MessageQueue有个mIdleHandlers队列,在获取下个要执行的Message时,如果没有发现可执行的下个Msg,就会回调queueIdle()方法。

找到引用关系


  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来得到主线程空闲回调


上一篇 下一篇

猜你喜欢

热点阅读