分析内存泄漏分析工具
核心一是ReferenceQueue,这是一个用来保存Reference的队列,具体见WeakReference的构造方法:
public class WeakReference<T> extends Reference<T> {
public WeakReference(T var1) {
super(var1);
}
public WeakReference(T var1, ReferenceQueue<? super T> var2) {
super(var1, var2);
}
}先了解WeakReference和ReferenceQueue:
WeakReference和ReferenceQueue
首先需要知道WeakReference。当GC线程扫描它所管辖的内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否(这一点与SoftReference不同),都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
WeakReference和ReferenceQueue联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。上图中的实线a表示强引用,虚线aa表示弱引用。如果切断a,那么Object对象将会被回收。正如下面这段测试代码所示。
A a = new A();
ReferenceQueue queue = new ReferenceQueue();
WeakReference aa = new WeakReference(a, queue);
a = null;
Runtime.getRuntime().gc();
System.runFinalization();
try {
} catch (InterruptedException e) {
e.printStackTrace();
}
Reference poll = null;
while ((poll = queue.poll()) != null) {
System.out.println(poll.toString());
}
当垃圾回收器回收对象的时候,aa这个弱引用将会入队进入ReferenceQueue,所以queue.poll()将不会为空,除非这个对象没有被垃圾回收器清理。ok了,开始分析程序。
程序入口
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}关键在buildAndInstall这个方法
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);//关键代码
}
return refWatcher;
}看install
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);//4.0提供的生命周期的监测
}看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);//在Activity Destroy的时候去监测内存泄漏
}
};看onActivityDestroyed:
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}到这里,可以看到leakcanary在4.0以上系统的监测是通过ActivityLifecycleCallbacks来监测的,监测时机是在onActivityDestroyed。具体监测方法是在refWatcher,继续往下看。
refWatcher#watch
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();//生成唯一key
retainedKeys.add(key);//retainedKeys中添加这个key
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//包装成WeakReference并添加到ReferenceQueue
ensureGoneAsync(watchStartNanoTime, reference);
}主要就是封装。且用retainedKeys这个set来保存每个引用的标识key。后面将会用到。封装完成就要开始分析了,核心方法是ensureGone:
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)) {//好,回收了,那么这个activity没有泄漏
return DONE;
}
gcTrigger.runGc();//还是没有回收,手动GC一下
removeWeaklyReachableReferences();//再看看对象回收没有
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
//竟然还没回收,那么怀疑是内存泄漏了,所以下一步dump内存快照.hprof下来进一步精确分析
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;
}来看removeWeaklyReachableReferences这个方法,是如何确定是否回收。
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}1、如果这个对象作为弱引用,被回收了,那么java虚拟机会自动将他添加到引用队列(ReferenceQueue)当中。
2、这个时候去queue.poll,将拿到弱引用ref(Activity)对应的key,retainedKeys去删除掉这个key(之前有往里面添加)。表明该对象的引用已经释放。
3、再用gone(reference)来确定retainedKeys是否已经删除对应的key了。
4、如果没有删除,则手动再gc一次(之前都是系统回收)。
5、再确认一下,如果还没有回收(即retainedKeys中还有这个key),则dump下来内存(.hprof文件)进行分析。
.hprof内存分析
通过上面的二次确认,可以确定某个object(Activity或者fragment)存在内存泄漏。那么接下来就是dump下来内存并进行analyze。
File heapDumpFile = heapDumper.dumpHeap();
//竟然还没回收,那么怀疑是内存泄漏了,所以下一步dump内存快照.hprof下来进一步精确分析
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));调用ServiceHeapDumpListener#analyze
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}最终调用到HeapAnalyzerService#onHandleIntent,其中HeapAnalyzerService是独立运行在:leakcanary进程:
@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);
//这里分析heapDump
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
//这里将分析结果发送出去做展示
}着重在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);
//1.把.hprof转为Snapshot,这个Snapshot对象就包含了对象引用的所有路径
HprofParser parser = new HprofParser(buffer);
//2.精简gcroots??
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
//3.找出泄漏的对象
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
//4.找出泄漏对象的最短路径
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}上面的checkForLeak方法就是输入.hprof,输出分析结果,主要有以下几个步骤:
1.把.hprof转为Snapshot,这个Snapshot对象就包含了对象引用的所有路径
2.精简gcroots,把重复的路径删除,重新封装成不重复的路径的容器
3.找出泄漏的对象
4.找出泄漏对象的最短路径
重点分析放在第3、4步:(很多处理逻辑都是haha这个第三方库实现的)
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);
}那么上面这个方法是在snapshot快照中找到第一个弱引用(因为就是这个对象没有回收,泄漏了),然后根据遍历这个对象的所有实例,如果key值和最开始定义封装的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));
}最后一个步骤是根据上一部得到的泄漏对象找到最短路径,封装在ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
总结:在第三个分析步骤,解析hprof文件中,是先把这个文件封装成snapshot,然后根据弱引用和前面定义的key值,确定泄漏的对象,最后找到最短泄漏路径,作为结果反馈出来,那么如果在快照中找不到这个怀疑泄漏的对象,那么就认为这个对象其实并没有泄漏,因为已经回收了,如下的代码:
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}总结、RefWatcher工作流程:
- RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
- 然后在后台线程检查引用是否被清除,如果没有,调用GC。
- 如果引用还是未被清除,把 heap 内存 dump 到 文件系统中的一个 .hprof 文件中。
- 在另外一个进程中,HeapAnalyzerService 通过 HeapAnalyzer 使用HAHA 解析这个文件。
- 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄漏。
- HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否泄漏。如果是,建立导致泄漏的引用链。
- 引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
以上,就是leakCanary的全部流程。