接上文,我们得到ForkAnalyseProcessor中,主要是通过dumpAndAnalyse来处理发现的内存泄漏问题,在该函数内,主要分为以下几步:
- prepareHprofFile:创建hprof文件
- MemoryUtil.dump:生成hprof文件内容
- analyze:分析hprof文件
- publishIssue:报告问题
- 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();
}