美文网首页
android 使用 LeakCanary 分析内存泄漏原理(二

android 使用 LeakCanary 分析内存泄漏原理(二

作者: 零星瓢虫 | 来源:发表于2020-06-10 16:17 被阅读0次

    LeakCanary 是 Square 公司基于 MAT 开源的一个工具,用来检测 Android App 中的内存泄露问题。官方地址:https://github.com/square/leakcanary

    上一篇文章分析了 android 开发可能会出现的内存的泄漏的情况,本篇文章主要来看看LeakCanary 是如何去分析代码中可能出现内存泄漏的原因。
    android 开发中可能会引起内存泄漏的情况

    LeakCanary 的简单用法

    首先,build.gradle 中配置依赖库;

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'androidx.appcompat:appcompat:1.0.2'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
        releaseImplementation'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    }
    

    Application 开启检测:

    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            // 注册 LeakCanary
            LeakCanary.install(this);
    
        }
    }
    

    在 Application 中调用 install 方法之后,就可以检测我们 app 运行时候的内存泄漏情况。

    分析 LeakCanary.install(this) 方法做了什么?

    接下来我们就该详细分析 install 方法主要做了什么,进入LeakCanary类中:

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

    调用 buildAndInstall() 方法返回 RefWatcher 类:

     public RefWatcher buildAndInstall() {
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
          LeakCanary.enableDisplayLeakActivity(context);
          ActivityRefWatcher.install((Application) context, refWatcher);
        }
        return refWatcher;
      }
    

    RefWatcher 用来观察 Activity 的释放资源,并分析内存泄漏情况。LeakCanary.enableDisplayLeakActivity(context) 方法是提示内存泄漏的弹框提醒。

    继续查看 ActivityRefWatcher.install((Application) context, refWatcher) 的方法:

      public static void install(Application application, RefWatcher refWatcher) {
        new ActivityRefWatcher(application, refWatcher).watchActivities();
      }
    

    进入 watchActivities 方法 :

     public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
      public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    

    watchActivities 方法先取消注册之前的的监听方法,然后注册此次运行Activity的内存泄漏的观察。

    接下来看下 lifecycleCallbacks 这个类:

      private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new Application.ActivityLifecycleCallbacks() {
            @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
    
            @Override public void onActivityStarted(Activity activity) {
            }
    
            @Override public void onActivityResumed(Activity activity) {
            }
    
            @Override public void onActivityPaused(Activity activity) {
            }
    
            @Override public void onActivityStopped(Activity activity) {
            }
    
            @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
    
            @Override public void onActivityDestroyed(Activity activity) {
              ActivityRefWatcher.this.onActivityDestroyed(activity);
            }
          };
    

    最后一行,onActivityDestroyed 的销毁方法中 ActivityRefWatcher 会调用 onActivityDestroyed 方法:

      void onActivityDestroyed(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 Set<String> retainedKeys;
      private final ReferenceQueue<Object> queue;
      private final HeapDump.Listener heapdumpListener;
      private final ExcludedRefs excludedRefs;
    }
    

    WatchExecutor watchExecutor:查找检测内存泄露的对象
    DebuggerControl debuggerControl:检测当前是否正在调试中
    GcTrigger gcTrigger:调用gc方法
    HeapDumper heapDumper:dump内存泄露产生的文件
    SetretainedKeys:存储引用key(待检测或已经产生泄露)
    ReferenceQueue queue:引用队列,存储待检测的弱引用
    HeapDump.Listener heapdumpListener:HeapDumper的回调
    ExcludedRefs excludedRefs:排除系统引起的内存泄露
    boolean computeRetainedHeapSize:检测泄露时是否计算堆的大小,默认为false

    接下来继续看 RefWatcher 类的 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);
      }
    
    

    watch 方法中传入的 Acitivity 对象,此是通过生成唯一号,给此销毁的 Activity 创建一个 KeyedWeakReference 弱引用的对象,然后调用 ensureGoneAsync(watchStartNanoTime, reference) 方法:

     Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
        removeWeaklyReachableReferences();// 删除可达到的弱引用
    
        if (debuggerControl.isDebuggerAttached()) { // 是否是调式模式
          // The debugger can create false leaks.
          return RETRY;
        }
        if (gone(reference)) { // 如果不包含key ,即 activity 的弱引用被删了。 则可以完全回收不必担心内存泄漏
          return DONE;
        }
        gcTrigger.runGc(); // 相当于手动 gc 
        removeWeaklyReachableReferences();
        if (!gone(reference)) {// 如果 activity 还存在弱引用中,则可能出现泄漏
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
    
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == RETRY_LATER) {
            // Could not dump the heap.
            return RETRY;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
          heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
        }
        return DONE;
      }
    
    
      private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
      }
    

    我们在 Activity 进行 destory 的时候把 Activity 放在一个 弱引用中,ensureGone 方法会尝试删除弱引用中的数据,如果发现 Activity 可以被删掉,那么不存在内存泄漏的情况。 如果 Activity 不能被从弱引用中成功删除,再次代码调用 gcTrigger.runGc() 执行 GC 方法,进行检测 Activity 的弱引用是否还存在。此时,如果 Activity 依然不能被成功删除(可能有其他对象对其引用),则可能存在内存泄漏情况。启动 heapdumpListener.analyze() 方法进行内存泄漏分析。

    对上面先做一个小的总结,程序运行到这里主要做了哪些工作?
    1 创建一个 refwatcher ,启动一个ActivityRefWatcher。
    2 通过 ActivityLiftcycleCallbacks 把 Activity 的 ondestory 生命周期关联。
    3 线程池中分析我们的泄漏情况。

    接下里继续分析 heapdumpListener.analyze(...) 方法:

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

    analyze 方法的实现类在 ServiceHeapDumpListener 中,继续执行 HeapAnalyzerService.runAnalysis 方法:

    public final class HeapAnalyzerService extends IntentService {
    
      private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
      private static final String HEAPDUMP_EXTRA = "heapdump_extra";
    
      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());
      }
    
      @Override protected void onHandleIntent(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);
    
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
      }
    }
    

    HeapAnalyzerService 是 IntentService 的父类,则这里会执行到 onHandleIntent 处理的方法,onHandlerIntent 方法主要做了一下事情:
    1 创建 HeapAnalyzer 对象,这里 heapDump.excludedRefs 排除了系统引起的雷村泄漏。
    2 执行 heapAnalyzer.checkForLeak 方法。

    HeapAnalyzer 明显是分析内存泄漏的类,进入 checkForLeak 方法:

      public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
        long analysisStartNanoTime = System.nanoTime();
    
        if (!heapDumpFile.exists()) {
          Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
          return failure(exception, since(analysisStartNanoTime));
        }
    
        try {
          HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
          HprofParser parser = new HprofParser(buffer);
          Snapshot snapshot = parser.parse();
          deduplicateGcRoots(snapshot);
    
          Instance leakingRef = findLeakingReference(referenceKey, snapshot);
    
          // False alarm, weak reference was cleared in between key check and heap dump.
          if (leakingRef == null) {
            return noLeak(since(analysisStartNanoTime));
          }
    
          return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
        } catch (Throwable e) {
          return failure(e, since(analysisStartNanoTime));
        }
      }
    

    checkForLeak 这里看到如果 heapDumpFile 不存在会抛出异常。存在的情况下:
    1 .hprof 转化成 Snapshot。
    2 deduplicateGcRoots(snapshot) 去除重复的泄漏文件。
    3 findLeakingReference 找出内存泄漏的引用和 findLeakTrace 找出内存泄漏的路径。

    findLeakingReference 获取内存泄漏对象的方法:

    private Instance findLeakingReference(String key, Snapshot snapshot) {
        ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
        List<String> keysFound = new ArrayList<>();
        for (Instance instance : refClass.getInstancesList()) {
          List<ClassInstance.FieldValue> values = classInstanceValues(instance);
          String keyCandidate = asString(fieldValue(values, "key"));
          if (keyCandidate.equals(key)) {
            return fieldValue(values, "referent");
          }
          keysFound.add(keyCandidate);
        }
        throw new IllegalStateException(
            "Could not find weak reference with key " + key + " in " + keysFound);
      }
    

    napshot.findClass(KeyedWeakReference.class.getName()) 通过查找弱引用的对象,然后通过遍历该弱引用的所有实例,当发现 key 值相等的,返回泄漏对象并存储到集合中。

    findLeakTrace 方法获取内存泄漏对象的路径:

    
      private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
          Instance leakingRef) {
    
        ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
        ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    
        // False alarm, no strong reference path to GC Roots.
        if (result.leakingNode == null) {
          return noLeak(since(analysisStartNanoTime));
        }
    
        LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
    
        String className = leakingRef.getClassObj().getClassName();
    
        // Side effect: computes retained size.
        snapshot.computeDominators();
    
        Instance leakingInstance = result.leakingNode.instance;
    
        long retainedSize = leakingInstance.getTotalRetainedSize();
    
        // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
        if (SDK_INT <= N_MR1) {
          retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
        }
    
        return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
            since(analysisStartNanoTime));
      }
    

    通过buildLeakTrace方法 ShortestPathFinder 对象找到内存泄漏的路径,同时 retainedSize 表示内存泄漏的空间大小。

    到这里就完成内存泄漏信息分析,并通过 leakDetected 方法将内存泄漏的结果信息反馈出来。


    LeakCanary分析.png

    相关文章

      网友评论

          本文标题:android 使用 LeakCanary 分析内存泄漏原理(二

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