从Matrix-ResourceCanary看内存快照生成-ForkAnalyseProcessor(2)

58 阅读4分钟

接上文,我们得到ForkAnalyseProcessor中,主要是通过dumpAndAnalyse来处理发现的内存泄漏问题,在该函数内,主要分为以下几步:

  1. prepareHprofFile:创建hprof文件
  2. MemoryUtil.dump:生成hprof文件内容
  3. analyze:分析hprof文件
  4. publishIssue:报告问题
  5. hprof.delete():删除hprof文件

其中prepareHprofFile和MemoryUtil.dump我们已经分析完了,接下来继续分析analyze流程。

analyze

analyze实现代码如下所示:

 protected ActivityLeakResult analyze(File hprofFile, String referenceKey) {
     setAnalyzing(true);
     final HeapSnapshot heapSnapshot;
     ActivityLeakResult result;
     String manufacture = Matrix.with().getPluginByClass(ResourcePlugin.class).getConfig().getManufacture();
     final ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults(Build.VERSION.SDK_INT, manufacture).build();
     try {
         heapSnapshot = new HeapSnapshot(hprofFile);
         result = new ActivityLeakAnalyzer(referenceKey, excludedRefs).analyze(heapSnapshot);
     } catch (IOException e) {
         result = ActivityLeakResult.failure(e, 0);
     }
     getWatcher().triggerGc();
     setAnalyzing(false);
     return result;
 }

可以看到这里的主要逻辑excludedRefs对象生成,heapSnapshot对象生成和ActivityLeakAnalyzer.analyze三部分沟通。

excludedRefs对象生成

在AndroidExcludedRefs中主要定义的是一些可以豁免的泄漏情景,这些场景主要是由框架机制引起的,比如InputMethodManager的View泄漏等,针对不同的Android系统版本添加有效的豁免规则,最终形成ExcludedRefs对象。

heapSnapshot对象生成
 public class HeapSnapshot {
 ​
     private final File mHprofFile;
     private final Snapshot mSnapshot;
 ​
     public HeapSnapshot(File hprofFile) throws IOException {
         mHprofFile = checkNotNull(hprofFile, "hprofFile");
         mSnapshot = initSnapshot(hprofFile);
     }
 ​
     public File getHprofFile() {
         return mHprofFile;
     }
 ​
     public Snapshot getSnapshot() {
         return mSnapshot;
     }
 ​
     private static Snapshot initSnapshot(File hprofFile) throws IOException {
         final HprofBuffer buffer = new MemoryMappedFileBuffer(hprofFile);
         final HprofParser parser = new HprofParser(buffer);
         final Snapshot result = parser.parse();
         AnalyzeUtil.deduplicateGcRoots(result);
         return result;
     }
 }

HeapSnapShot对象定义如上所示,可以看到在HeapSnapShot构造函数中,进行了核心逻辑执行,从代码来看这里主要包含四个步骤,生成HprofBuffer对象,生成HprofParser对象,执行HprofParser对象的parse方法生成SnapShot对象和根据GcRoots去重四个步骤。

生成HprofBuffer对象

从代码可以看出HprofBuffer对象是MemoryMappedFileBuffer类的实例,该类位于HaHa库中,主要用于实现对Hprof文件读写操作的包装,与LeakCanary中的shark-hprof模块的作用是一样的,LeakCanary早期版本也是通过HaHa库来读写hprof文件的,目前HaHa库已废弃,这里不做赘述。

详细代码大家可以在github查看:MemoryMappedFileBuffer

生成HprofParser对象

同样的HprofParser类也位于HaHa库中,主要用于进行hprof文件的内容解析,代码链接:HprofParser

HprofParser.parse

HprofParser.parse方法主要代码如下所示:

 public final Snapshot parse() {
     Snapshot snapshot = new Snapshot(mInput);
     mSnapshot = snapshot;
 ​
     try {
         try {
             readNullTerminatedString();  // Version, ignored for now.
 ​
             mIdSize = mInput.readInt();
             mSnapshot.setIdSize(mIdSize);
 ​
             mInput.readLong();  // Timestamp, ignored for now.
 ​
             while (mInput.hasRemaining()) {
                 int tag = readUnsignedByte();
                 mInput.readInt(); // Ignored: timestamp
                 long length = readUnsignedInt();
 ​
                 switch (tag) {
                     case STRING_IN_UTF8:
                         // String length is limited by Int.MAX_VALUE anyway.
                         loadString((int) length - mIdSize);
                         break;
 ​
                     case LOAD_CLASS:
                         loadClass();
                         break;
 ​
                     case STACK_FRAME:
                         loadStackFrame();
                         break;
 ​
                     case STACK_TRACE:
                         loadStackTrace();
                         break;
 ​
                     case HEAP_DUMP:
                         loadHeapDump(length);
                         mSnapshot.setToDefaultHeap();
                         break;
 ​
                     case HEAP_DUMP_SEGMENT:
                         loadHeapDump(length);
                         mSnapshot.setToDefaultHeap();
                         break;
 ​
                     default:
                         skipFully(length);
                 }
 ​
             }
         } catch (EOFException eof) {
             //  this is fine
         }
         mSnapshot.resolveClasses();
         mSnapshot.resolveReferences();
         // TODO: enable this after the dominators computation is also optimized.
         // mSnapshot.computeRetainedSizes();
     } catch (Exception e) {
         e.printStackTrace();
     }
 ​
     mClassNames.clear();
     mStrings.clear();
     return snapshot;
 }

这里生成的SnapShot对象同样也是HaHa库中提供的,主要用于进行GC Roots等相关信息的管理。详细代码链接:Snapshot

deduplicateGcRoots

deduplicateGcRoots代码实现如下所示,可以看到这里主要是根据SnapShot中解析到的GcRoots进行去重v操作。

 public static void deduplicateGcRoots(Snapshot snapshot) {
     // THashMap has a smaller memory footprint than HashMap.
     final THashMap<String, RootObj> uniqueRootMap = new THashMap<>();
 ​
     final Collection<RootObj> gcRoots = snapshot.getGCRoots();
     for (RootObj root : gcRoots) {
         String key = generateRootKey(root);
         if (!uniqueRootMap.containsKey(key)) {
             uniqueRootMap.put(key, root);
         }
     }
 ​
     // Repopulate snapshot with unique GC roots.
     gcRoots.clear();
     uniqueRootMap.forEach(new TObjectProcedure<String>() {
         @Override
         public boolean execute(String key) {
             return gcRoots.add(uniqueRootMap.get(key));
         }
     });
 }
ActivityLeakAnalyzer.analyze

ActivityLeakAnalyzer.analyze的代码如下,可以看到其主要是通过查找DestroyedActivityInfo实例并找到其关联Activity的Gc Root path来实现泄漏对象的定位和结果上报,当拿到泄漏对象的Gc path后通过ActivityLeakResult向外传递。

 public class ActivityLeakAnalyzer implements HeapSnapshotAnalyzer<ActivityLeakResult> {
     private static final String DESTROYED_ACTIVITY_INFO_CLASSNAME
             = "com.tencent.matrix.resource.analyzer.model.DestroyedActivityInfo";
     private static final String ACTIVITY_REFERENCE_KEY_FIELDNAME = "mKey";
     private static final String ACTIVITY_REFERENCE_FIELDNAME = "mActivityRef";
 ​
     private final String mRefKey;
     private final ExcludedRefs mExcludedRefs;
 ​
     public ActivityLeakAnalyzer(String refKey, ExcludedRefs excludedRefs) {
         mRefKey = refKey;
         mExcludedRefs = excludedRefs;
     }
 ​
     @Override
     public ActivityLeakResult analyze(HeapSnapshot heapSnapshot) {
         return checkForLeak(heapSnapshot, mRefKey);
     }
 ​
     /**
      * 查找DestroyedActivityInfo实例并找到其关联Activity的Gc Root path
      */
     private ActivityLeakResult checkForLeak(HeapSnapshot heapSnapshot, String refKey) {
         long analysisStartNanoTime = System.nanoTime();
 ​
         try {
             final Snapshot snapshot = heapSnapshot.getSnapshot();
             final Instance leakingRef = findLeakingReference(refKey, snapshot);
 ​
             // False alarm, weak reference was cleared in between key check and heap dump.
             if (leakingRef == null) {
                 return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime));
             }
 ​
             return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
         } catch (Throwable e) {
             e.printStackTrace();
             return ActivityLeakResult.failure(e, AnalyzeUtil.since(analysisStartNanoTime));
         }
     }
 ​
     private Instance findLeakingReference(String key, Snapshot snapshot) {
         final ClassObj infoClass = snapshot.findClass(DESTROYED_ACTIVITY_INFO_CLASSNAME);
         if (infoClass == null) {
             throw new IllegalStateException("Unabled to find destroy activity info class with name: "
                     + DESTROYED_ACTIVITY_INFO_CLASSNAME);
         }
         List<String> keysFound = new ArrayList<>();
         // 遍历Snapshot中的所有DestroyedActivityInfo实例
         for (Instance infoInstance : infoClass.getInstancesList()) {
             final List<ClassInstance.FieldValue> values = classInstanceValues(infoInstance);
             final String keyCandidate = asString(fieldValue(values, ACTIVITY_REFERENCE_KEY_FIELDNAME));
             if (keyCandidate.equals(key)) {
                 final Instance weakRefObj = fieldValue(values, ACTIVITY_REFERENCE_FIELDNAME);
                 if (weakRefObj == null) {
                     continue;
                 }
                 final List<ClassInstance.FieldValue> activityRefs = classInstanceValues(weakRefObj);
                 // 返回泄漏的Activity对象实例
                 return fieldValue(activityRefs, "referent");
             }
             keysFound.add(keyCandidate);
         }
         throw new IllegalStateException(
                 "Could not find weak reference with key " + key + " in " + keysFound);
     }
 ​
     private ActivityLeakResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
                                          Instance leakingRef) {
 ​
         ShortestPathFinder pathFinder = new ShortestPathFinder(mExcludedRefs);
         // 查找Activity的Gc path
         ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
 ​
         // False alarm, no strong reference path to GC Roots.
         if (result.referenceChainHead == null) {
             return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime));
         }
 ​
         final ReferenceChain referenceChain = result.buildReferenceChain();
         final String className = leakingRef.getClassObj().getClassName();
         if (result.excludingKnown || referenceChain.isEmpty()) {
             return ActivityLeakResult.noLeak(AnalyzeUtil.since(analysisStartNanoTime));
         } else {
             return ActivityLeakResult.leakDetected(false, className, referenceChain,
                     AnalyzeUtil.since(analysisStartNanoTime));
         }
     }
 }

publishIssue和hprof.delete()

这两个代码逻辑就比较简单了,前文中我们已经看见过了,代码如下:

 try {
     final long analyseStart = System.currentTimeMillis();
 ​
     final ActivityLeakResult leaks = analyze(hprof, key);
     MatrixLog.i(TAG, String.format("analyze cost=%sms refString=%s",
             System.currentTimeMillis() - analyseStart, key));
 ​
     if (leaks.mLeakFound) {
         final String leakChain = leaks.toString();
         // publishIssue
         publishIssue(
                 SharePluginInfo.IssueType.LEAK_FOUND,
                 ResourceConfig.DumpMode.FORK_ANALYSE,
                 activity, key, leakChain,
                 String.valueOf(System.currentTimeMillis() - dumpStart));
         MatrixLog.i(TAG, leakChain);
     } else {
         MatrixLog.i(TAG, "leak not found");
     }
 ​
 } catch (OutOfMemoryError error) {
     publishIssue(
             SharePluginInfo.IssueType.ERR_ANALYSE_OOM,
             ResourceConfig.DumpMode.FORK_ANALYSE,
             activity, key, "OutOfMemoryError",
             "0");
     MatrixLog.printErrStackTrace(TAG, error.getCause(), "");
 } finally {
     //noinspection ResultOfMethodCallIgnored
     hprof.delete();
 }