32

LeakCanary原理

 4 years ago
source link: https://www.tuicool.com/articles/QveuUbi
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

LeakCanary原理

前言

Leakcanary是由Square公司开源的一款轻量的第三方检测内存泄露的工具

主要原理 watch一个即将要销毁的对象,比如监控一个activity处于什么状态。

先来看一下java内存中几个比较重要的部分

  • 栈(stack) 存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中

  • 堆(heap) 主要存放用new产生的数据,是垃圾回收器主要回收的部分

  • 方法区 存储每个类的信息(包括类的名称、方法信息、字段信息)静态变量、常量以及编译器变异后的的代码等

为什么会产生内存泄露

当一个对象已经不再使用了,本应该回收,但是一个能到达GCRoot的对象还持有它的引用,导致它无法被回收,还停留在堆内存中,导致内存泄漏

LeakCanary原理:

  • 当一个Activity Destory之后,将它放在一个WeakReference弱引用中中
  • 把这个WeakReference关联到一个ReferenceQueue
  • 查看ReferenceQueue中是否存在Activity的引用
  • 如果Activity泄露了,就Dump出heap信息,然后去分析内存泄露的路径

java中的4中引用类型

  • 强引用:不会被GC回收
  • 软引用:内存不足的时候会被GC回收
  • 弱引用:当下次GC的时候会回收
  • 虚引用:任何情况都可以回收

ReferenceQueue 引用队列 软引用和弱引用都可以和它集合使用,如果软引用或者弱引用中的对象被垃圾回收了,java虚拟机会吧这个引用加入到与之关联的引用队列当中。

LeakCanary源码分析

一般是在Application的onCreate方法中初始化

@Override
  public void onCreate() {
      super.onCreate();
      if (LeakCanary.isInAnalyzerProcess(this)) {
          return;
      }
      LeakCanary.install(this);
  }

进入install方法

public static @NonNull RefWatcher install(@NonNull Application application) {
   return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
       .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
       .buildAndInstall();
 }

返回一个RefWatcher对象,这个对象是用来监视应该成为弱引用的对象。最终通过buildAndInstall()这个方法创建出来。

public @NonNull RefWatcher buildAndInstall() {
  if (LeakCanaryInternals.installedRefWatcher != null) {
    throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
  }
  RefWatcher refWatcher = build();
  //如果是在别的进程中,会跟DISABLED相等
  if (refWatcher != DISABLED) {
    if (enableDisplayLeakActivity) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
    }
    //默认为true
    if (watchActivities) {
      ActivityRefWatcher.install(context, refWatcher);
    }
    //默认为true
    if (watchFragments) {
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;
}

通过build方法创建出RefWatcher,如果是别的进程,就直接返回成员变量DISABLED,如果不是创建新的RefWatcher并返回。

如果允许显示内存泄露的Activity,就设置可显示,DisplayLeakActivity就是当有内存泄露的时候,LeakCanary给我们提供的可视化的那个界面

分别创建ActivityRefWatcher和FragmentRefWatcher,首先看ActivityRefWatcher.install方法

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
  Application application = (Application) context.getApplicationContext();
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

  application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
      @Override public void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    };

通过传入的context拿到Application ,并创建ActivityRefWatcher ,最后通过application注册Activity的生命周期回到函数,并传入自己的callback。ActivityLifecycleCallbacksAdapter继承自Android系统的Application.ActivityLifecycleCallbacks接口,主要为了简化代码,因为只用到了onActivityDestroyed这一个方法。这个接口是Andorid系统为我们提供的可以监听到每个Activity的生命周期。

在回调函数中可以看到,当监听到一个Activity销毁的时候,就通过refWatcher.watch(activity)方法把这个Activity关联到RefWatcher中。

在查看watch方法之前,先来看一下RefWatcher有哪些成员变量

public final class RefWatcher {

  public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();

  private final WatchExecutor watchExecutor;
  private final DebuggerControl debuggerControl;
  private final GcTrigger gcTrigger;
  private final HeapDumper heapDumper;
  private final HeapDump.Listener heapdumpListener;
  private final HeapDump.Builder heapDumpBuilder;
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue;
  
  ....
  • WatchExecutor: 用于执行内存泄露的检测
  • DebuggerControl: 判断是否是调试状态,调试状态是不用检测内存泄露的
  • GcTrigger: 用来处理GC,当检测到一个对象可能会内存泄露的时候,它会调用其中的方法在手动GC一下,看是否能回收这个对象,如果不能回收那这个对象就泄露了
  • HeapDumper: dump出内存泄露的堆文件
  • HeapDump.Listener: 用来分析产生heap文件的回调
  • HeapDump.Builder:HeapDump的构建者对象
  • Set: 集合,持有待检测的和已经产生内存泄露的引用的key
  • ReferenceQueue: 判断弱引用所持有的对象是否执行了GC垃圾回收

OK,现在去查看watch方法

public void watch(Object watchedReference) {
   watch(watchedReference, "");
 }
public void watch(Object watchedReference, String referenceName) {
   if (this == DISABLED) {
     return;
   }
   checkNotNull(watchedReference, "watchedReference");
   checkNotNull(referenceName, "referenceName");
   final long watchStartNanoTime = System.nanoTime();
   String key = UUID.randomUUID().toString();
   retainedKeys.add(key);
   final KeyedWeakReference reference =
       new KeyedWeakReference(watchedReference, key, referenceName, queue);

   ensureGoneAsync(watchStartNanoTime, reference);
 }
 final class KeyedWeakReference extends WeakReference<Object> {}

创建了一个唯一key,然后放到成员变量set中保存,之后创建了一个KeyedWeakReference这个弱引用。用来保存需要分析的对象,最后执行异步方法ensureGoneAsync来分析这个弱引用对象。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

在子线程中执行ensureGone方法来分析对象是否真的被回收了

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  //从我们调用watch方法到现在的总共使用的时间
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  //清除set集合中 已经到达引用队列中的弱引用
  removeWeaklyReachableReferences();
  //如果在调试状态 就不需要分析
  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }
  //改对象没有造成内存泄露
  if (gone(reference)) {
    return DONE;
  }
  //手动调用GC
  gcTrigger.runGc();
  //再次 清除set集合中 已经到达引用队列中的弱引用
  removeWeaklyReachableReferences();
  //如果此时引用集合set中还包含改对象,那么它就是个内存泄露的对象
  if (!gone(reference)) {
    long startDumpHeap = System.nanoTime();
    long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
   //demp出一个 .hprof文件
    File heapDumpFile = heapDumper.dumpHeap();
    if (heapDumpFile == RETRY_LATER) {
      // Could not dump the heap.
      return RETRY;
    }
    long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
   
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();
    //分析内存泄露
    heapdumpListener.analyze(heapDump);
  }
  return DONE;
}

总结一下前面的代码:

  • 创建一个RefWatcher并启动一个ActivityRefWatcher
  • 通过ActivityLifecycleCallbacks接口,监听activity的回调,在onDestory中去将activity对象放入观察引用中去观察
  • 先清除引用队列中的弱引用,接着检查对象是否到达引用队列,然后手动执行GC,如果GC完后还有未被回收的对象,调用analyze方法分析内存泄露
public interface Listener {
   Listener NONE = new Listener() {
     @Override public void analyze(HeapDump heapDump) {
     }
   };

   void analyze(HeapDump heapDump);
 }
 
 @Override public void analyze(@NonNull HeapDump heapDump) {
   checkNotNull(heapDump, "heapDump");
   HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
 }

analyze是Listener接口中的一个方法,它的实现类是在ServiceHeapDumpListener中。最后调用了HeapAnalyzerService.runAnalysis方法。

public static void runAnalysis(Context context, HeapDump heapDump,
     Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
   setEnabledBlocking(context, HeapAnalyzerService.class, true);
   setEnabledBlocking(context, listenerServiceClass, true);
   Intent intent = new Intent(context, HeapAnalyzerService.class);
   intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
   intent.putExtra(HEAPDUMP_EXTRA, heapDump);
   ContextCompat.startForegroundService(context, intent);
 }

HeapAnalyzerService 继承自 ForegroundService , ForegroundService 继承自 IntentService,runAnalysis方法中就是开启了一个前台的IntentService。最后会执行IntentService的onHandleIntent方法,这里面又执行了抽象方法onHandleIntentInForeground,这个方法在HeapAnalyzerService类中实现。

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
  if (intent == null) {
    CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
    return;
  }
  String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
  HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

  HeapAnalyzer heapAnalyzer =
      new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

  AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
      heapDump.computeRetainedHeapSize);
  AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

从intent中拿到className和HeapDump,然后通过HeapAnalyzer这个类的checkForLeak方法进行分析。最后通过sendResultToListener方法返回。

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);
    //去除重复的内容
    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));
    }
    //找到泄露对象的最短路径
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
  } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
  }
}

checkForLeak方法就是LeakCanary中的核心方法了,这里面用到了Square的另一个开源库haha库,地址 https://github.com/square/haha

  • 通过HprofParser类将hprof转换为Snapshot内存快照。Snapshot中包含所有对象引用的路径,就能查找到内存泄露的路径了
  • 优化GCRoot 通过deduplicateGcRoots方法删除重复的路径
  • findLeakingReference找出泄露的对象
  • findLeakTrace方法找出泄露对象的最短路径

findLeakingReference方法

private Instance findLeakingReference(String key, Snapshot snapshot) {
  ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
  if (refClass == null) {
    throw new IllegalStateException(
        "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
  }
  List<String> keysFound = new ArrayList<>();
  for (Instance instance : refClass.getInstancesList()) {
    List<ClassInstance.FieldValue> values = classInstanceValues(instance);
    Object keyFieldValue = fieldValue(values, "key");
    if (keyFieldValue == null) {
      keysFound.add(null);
      continue;
    }
    String keyCandidate = asString(keyFieldValue);
    if (keyCandidate.equals(key)) {
      return fieldValue(values, "referent");
    }
    keysFound.add(keyCandidate);
  }
  throw new IllegalStateException(
      "Could not find weak reference with key " + key + " in " + keysFound);
}
  • 在内存快照Snapshot中找到第一个弱引用KeyedWeakReference,这就是内存泄露的对象。
  • 遍历这个对象的所有的实例
  • 如果找到的key值和最开始保存的key值一样,那么这个对象就是内存泄露的对象

findLeakTrace方法

private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef, boolean computeRetainedSize) {

    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

    String className = leakingRef.getClassObj().getClassName();

    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(className, since(analysisStartNanoTime));
    }

    listener.onProgressUpdate(BUILDING_LEAK_TRACE);
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    long retainedSize;
    if (computeRetainedSize) {

      listener.onProgressUpdate(COMPUTING_DOMINATORS);
      // Side effect: computes retained size.
      snapshot.computeDominators();

      Instance leakingInstance = result.leakingNode.instance;

      retainedSize = leakingInstance.getTotalRetainedSize();

      // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
      if (SDK_INT <= N_MR1) {
        listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
        retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
      }
    } else {
      retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
    }

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }

通过findPath方法,GCroot开始往下寻找

LeakTrace就是内存泄露的调用栈

getTotalRetainedSize()方法,计算内存泄露的内存空间大小

OK Activity的监控流程就看完啦,下面看一下Fragment的。其实跟Activity差不多。从install开始

public static void install(Context context, RefWatcher refWatcher) {
   List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
 // 如果大于Anroid 26,需要增加AndroidOFragmentRefWatcher
   if (SDK_INT >= O) {
     fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
   }
   // 通过反射添加SupportFragmentRefWatcher
   try {
     Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
     Constructor<?> constructor =
         fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
     FragmentRefWatcher supportFragmentRefWatcher =
         (FragmentRefWatcher) constructor.newInstance(refWatcher);
     fragmentRefWatchers.add(supportFragmentRefWatcher);
   } catch (Exception ignored) {
   }

   if (fragmentRefWatchers.size() == 0) {
     return;
   }

   Helper helper = new Helper(fragmentRefWatchers);

   Application application = (Application) context.getApplicationContext();
   application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
 }

通过反射找到SupportFragmentRefWatcher,它类需要在build.gradle中添加 debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'加入引用。

最后注册ActivityLifecycleCallbacks,来监听activity的回调

private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
       new ActivityLifecycleCallbacksAdapter() {
         @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
           for (FragmentRefWatcher watcher : fragmentRefWatchers) {
             watcher.watchFragments(activity);
           }
         }
       };

可以看到这里监听的是activity的onActivityCreated这个生命周期函数,然后把当前的activity的对象传入FragmentRefWatcher中,执行接口watchFragments。SupportFragmentRefWatcher和AndroidOFragmentRefWatcher是FragmentRefWatcher的实现类。最终会回调实现类的watchFragments方法

@Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }

通过Activity找到FragmentManager,然后注册系统的Fragment的生命周期回调监听

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }
        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

Fragment的回调主要监听了onFragmentViewDestroyed和onFragmentDestroyed两个回调方法。最终都会调用RefWatcher中的watch方法,这里面跟前面activity中是一样的啦。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK