概述
leakcanary是一个监测android和java内存泄漏的工具。他能够在不影响程序正常运行的情况下,动态收集程序存在的内存泄漏问题。
大体流程
- Activity Destory之后将它放在一个WeakReference
- 这个WeakReference关联到一个ReferenceQueue
- 查看ReferenceQueue是否存在Activity的引用
- 如果该Activity泄漏了,Dump出heap信息,然后再去分析泄漏路径。
引用类型
- 强引用(StrongReference) 不管内存是否不够分配都不会清除
- 软引用(SoftRefererence) 当内存不够用的时候会去主动清除当前内存
- 弱引用(WeakReference) 当垃圾回收器检测到弱引用,不管内存够不够都会清除它
- 虚引用 就相当于形同虚设的引用 任何情况都会回收它
对象被垃圾回收,Java虚拟机就会把这个引用加入到与之关联的引用队列中。
LeakCanary的使用
先从低版本2.0以下的版本 当前使用的是1.5
第一步 加入依赖包
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
第二步 注册Leakcanary
public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// 判断是否和 LeakCanary 初始化同一进程
return;
}
LeakCanary.install(this);//获取一个 Watcher
}
}
查看源码
public static RefWatcher install(Application application) {
//是启动Activity的RefWatcher 然后通过Activity的RefWatcher 通过执行ondestory 之后来监测Activity内存泄漏
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
buildAndInstall 继续往下看
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();//创建RefWatcher来启动Activity的RefWatcher 监听Activity的内存泄漏的情况
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);//这个enableDisplayLeakActivity 就是那个弹窗,告诉我们哪里有内存泄漏,开启DisplayLeakActivity
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
}
return refWatcher;
}
- 创建RefWatcher来启动Activity的RefWatcher 监听Activity的内存泄漏的情况
- 这个enableDisplayLeakActivity 就是那个弹窗,告诉我们哪里有内存泄漏,开启DisplayLeakActivity
installOnIcsPlus 继续往下看
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); //创建了ActivityRefWatcher
activityRefWatcher.watchActivities();
}
- 创建了ActivityRefWatcher
watchActivities 继续往下看
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();//就是反注册之前生命周期的Callback,保证之前所作的内存泄漏的工作都删除
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);//重新注册我们Activity生命周期的callback
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
- 反注册之前生命周期的Callback,保证之前所作的内存泄漏的工作都删除
- 重新注册我们Activity生命周期的callback
接下来看一下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) {
}
//发现在Activity调用onDestory这个方法之后所作的操作
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
//onActivityDestroyed这个方法会将我们之前定义好的ActivityLifecycleCallbacks 把我们Activity的ondestory这个方法以及他们的生命周期关联到了ActivityRefWatcher
}
};
- 发现在Activity调用onDestory这个方法之后所作的操作
- onActivityDestroyed这个方法会将我们之前定义好的ActivityLifecycleCallbacks 把我们Activity的ondestory这个方法以及他们的生命周期关联到了ActivityRefWatcher
onActivityDestroyed 做了哪些东西呢?
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
在了解watch方法之前,先看一下RefWatcher 这个类的成员变量都是什么
public final class RefWatcher {
private final WatchExecutor watchExecutor; //用于去执行我们的内存泄漏的检测用的
private final DebuggerControl debuggerControl;//用于查询我们是否正在调试中 如果正在调试中,就不会执行内存泄漏的检测判断
private final GcTrigger gcTrigger;//处理GC的 用于判断我们的内存泄漏之前会给我们泄漏对象再一次机会,会调用这个对象里面的方法去调用我们的gc,看它是否去gc,如果不能gc的话,就会把信息给显示出来
private final HeapDumper heapDumper;//dumper出我们内存泄漏的堆文件
private final Set<String> retainedKeys;//持有那些待检测的,以及那些已经产生了内存泄漏的引用的key
private final ReferenceQueue<Object> queue;//引用队列,主要用于判断弱引用持有的对象是否已经被执行gc垃圾回收。
private final HeapDump.Listener heapdumpListener;//主要用于分析一些产生heap文件的回调
private final ExcludedRefs excludedRefs;//排除一些系统引起的内存泄漏
...
}
分析完成员变量接下来看一下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(); //我们要对我们的Reference的引用添加一个唯一的key值
retainedKeys.add(key);//将我们获取到的key加入我们所说的set集合当中,也就是说加入到了我们待检测以及已经产生内存泄漏的key的set集合当中
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);//利用watch方法传入的对象,创建了我们需要的弱引用
ensureGoneAsync(watchStartNanoTime, reference);//开启一个异步线程来分析我们刚刚所创建好的Reference弱引用
}
- 要对我们的Reference的引用添加一个唯一的key值
- 将我们获取到的key加入我们所说的set集合当中,也就是说加入到了我们待检测以及已经产生内存泄漏的key的set集合当中
- 利用watch方法传入的对象,创建了我们需要的弱引用
- 开启一个异步线程来分析我们刚刚所创建好的Reference弱引用
接下来分析ensureGoneAsync 这个方法
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() { //在线程池中执行了该方法
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {//确保我们的Activity是否已经进入这个Gone这个状态,Gone这个状态表示这个Activity是否真的被回收了
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);//就是用来计算我们调用watch方法到gc回收的总共产生的时间
removeWeaklyReachableReferences();//清除此时已经到达我们的引用队列的弱引用,剩下的就是一些未被回收的引用,那么它就会通知我们这些就是我们应该回收的对象
if (debuggerControl.isDebuggerAttached()) {//如果此时处于debug的状态下,就不会进行内存泄漏的分析
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {//如果当前对象它已经可达了,也就是不会造成内存泄漏,告诉我们已经不用解决这个内存泄漏了
return DONE;
}
gcTrigger.runGc();//如果当前对象没有达到可达对象,来进行手动的垃圾回收
removeWeaklyReachableReferences();//再次清除已经到达我们的引用队列的弱引用
if (!gone(reference)) {//如果此时对象没有到达可达,预期被垃圾回收对象可能会出现在当前的对象当中,没有出现表示已经内存泄漏了
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();//这个时候就会dump出我们内存泄漏的那些堆文件
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;
}
- 确保我们的Activity是否已经进入这个Gone这个状态,Gone这个状态表示这个Activity是否真的被回收了
- 清除此时已经到达我们的引用队列的弱引用,剩下的就是一些未被回收的引用,那么它就会通知我们这些就是我们应该回收的对象
- 如果此时处于debug的状态下,就不会进行内存泄漏的分析
- 如果当前对象它已经可达了,也就是不会造成内存泄漏,告诉我们已经不用解决这个内存泄漏了
- 如果当前对象没有达到可达对象,来进行手动的垃圾回收
- 再次清除已经到达我们的引用队列的弱引用
- 如果此时对象没有到达可达,预期被垃圾回收对象可能会出现在当前的对象当中,没有出现的话则表示已经内存泄漏了
- 内存泄漏之后就会dump出我们内存泄漏的那些堆文件
- 分析内存泄漏
总结:
- 首先会创建一个Refwatcher,启动一个ActivityRefWatcher ,它会开启一个Activity的RefWatcher,就是用于监听我们Activity的回收情况
- 通过ActivityLifecycleCallbacks把Activity的onDestory生命周期关联
- 最后在线程池中去开始分析我们的泄漏
如何分析我们的内存泄漏
查看analyze
public final class HeapDump implements Serializable {
...
void analyze(HeapDump heapDump);//接口
...
}
接下来看一下具体实现
public final class ServiceHeapDumpListener implements HeapDump.Listener {
...
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
继续查看一下runAnalysis这个方法
public final class HeapAnalyzerService extends IntentService {
...
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);
}
@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);//分析我们的堆内存的 heapDump.excludedRefs排除系统存在的内存泄漏
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);//来分析内存使用结果
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);//调用一个回调来进行接口的显示
}
}
- 分析产生的堆文件
- 分析我们的堆内存的 heapDump.excludedRefs排除系统存在的内存泄漏
- 调用一个回调来进行接口的显示
接下来看一下checkForLeak 这个方法
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
//就是为了将hrof文件解析成我们snapshot对象
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);//把这个hrof文件封装成这个对象
HprofParser parser = new HprofParser(buffer);//hrof的解析器来解析上面传递过来的hrof文件的
Snapshot snapshot = parser.parse();//具体解析工作
deduplicateGcRoots(snapshot);//去重的GcRoots ,会对我们分析的结果进行去重,把那些重复的都删掉
Instance leakingRef = findLeakingReference(referenceKey, snapshot);//根据我们需要检测的类的referenceKey,来查询解析我们结果中是否有我们需要的对象,获取解析结果中检测的对象就是这个方法的意义
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {//如果表示为空的话就表示GC已经清除了
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);//如果不为空的话就会把我们的整个路径给找出来
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
总结checkForLeak
- 把hprof文件转为Snapshot(内存快照),由于Snapshot包含所有对象引用的路径,然后就能查找到它所需要的内存泄漏路径
- 优化gcroots 通过deduplicateGcRoots方法来删除重复的路径
- 找出泄漏的对象以及找出泄漏对象的最短路径
接下来分析findLeakingReference 如何找到内存泄漏的引用
private Instance findLeakingReference(String key, Snapshot snapshot) {
ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
//创建一个calss对象,通过findClass方法通过查找弱引用的对象就可以查找出内存泄漏的对象
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);
}
总结findLeakingReference
- 在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);
//通过分析hprof文件然后在找出内存泄漏的点
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {//找不到内存泄漏的话,返回一个noLeak的返回值
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();//就是用来计算内存泄漏的内存空间大小
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
总结findLeakTrace
- 解析hprof文件,把这个文件封装成snapshot
- 根据弱引用和前面定义的key值,确定泄漏的对象
- 通过key对象的引用找到最短泄漏路径,作为结果反馈出来