一、相关概念
1. 弱引用与引用队列
在WeakReference指向的对象在GC时被回收后,WeakReference本身其实也就没有用了,系统会把该弱引用对象加入到与之关联的ReferenceQueue中(当然前提是你创建该WeakReference的时候给其关联了ReferenceQueue)。
摘自 零度anngle 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/zmx729618/article/details/54093532?utm_source=copy
二、使用
- gradle中添加依赖
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
}
leakcanary-android-no-op是用于release版本的,因为LeakCanary会不断GC,造成stop the world 这在release版本中时不允许的,因此内存泄漏检测我们只能用在debug版本。
- 在Application中进行初始化操作
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
LeakCanary在主进程中检测内存泄漏,在另外的进程中分析hprof文件。多进程下Application被多次创建,为了防止在 Application中出现无用的重复初始化,在Application中可通过processName来过滤,只有在指定进程才做相应的初始化。
- 在Manifest中配置Application
<application
android:name=".ExampleApplication ">
....</application>
三、源码分析
3.1LeakCanary的准备工作
LeakCanary检测内存泄漏的入口:Application中的
LeakCanary.install(this);
该方法的主要作用是通过Builder模式构建RefWatcher实例,并做一些初始化的准备工作(为application注册ActivityLifecycleCallback)
public final class LeakCanary{
public static RefWatcher install(Application application){
// 采用构造器模式构造RefWatcher实例,链式调用
return ((AndroidRefWatcherBuilder)refWatcher(application)
.listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()))
.buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
}
1)得到AndroidRefWatcherBuilder
上述方法采用构建者模式,(AndroidRefWatcherBuilder extends RefWatcherBuilder
)最终构造出一个RefWatcher
实例,RefWatcher用来监视一个引用的可达情况
2).listenerServiceClass(DisplayLeakService.class)
public final class AndroidRefWatcherBuilder{
public AndroidRefWatcherBuilder listenerServiceClass(Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return (AndroidRefWatcherBuilder)this.heapDumpListener(new ServiceHeapDumpListener(this.context, listenerServiceClass));
}
}
上述AbstractAnalysisResultService
是一个抽象类,继承自IntentService
(public abstract class AbstractAnalysisResultService extends IntentService
),用于处理heapDump结果。
LeakCanary#install()
中传入的参数DiaplayLeakService
继承自AbstractAnalysisResultService
(public class DisplayLeakService extends AbstractAnalysisResultService
),运行在独立的进程中
<service
android:name=".DisplayLeakService"
android:process=":leakcanary"
android:enabled="false"
/>
3)excludedRefs(...)
有时候我们需要忽略一些特殊情况下的内存泄漏比如SDK或者制造商导致的内存泄漏,这些内存泄漏我们并不需要显示出来,那么刚好AndroidExcludedRefs这个枚举就派上用场了。
4)buildAndInstall()
public final class AndroidRefWatcherBuilder{
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = this.build();
if(refWatcher != RefWatcher.DISABLED) {
LeakCanary.enableDisplayLeakActivity(this.context);
// 调用ActivityRefWatcher.install()方法,将Application的上下文和RefWatcher传入
ActivityRefWatcher.install((Application)this.context, refWatcher);
}
return refWatcher;
}
}
构建出RefWatcher
实例,紧接着调用ActivityRefWatcher.install((Application)this.context, refWatcher);
,这是重点
public final class ActivityRefWatcher{
// ActivityLifecycleCallbacks:应用内Activity生命周期发生变化时会回调相应函数【该方法在API14后才有】
private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
public void onActivityStarted(Activity activity) {
}
public void onActivityResumed(Activity activity) {
}
public void onActivityPaused(Activity activity) {
}
public void onActivityStopped(Activity activity) {
}
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
void onActivityDestroyed(Activity activity) {
this.refWatcher.watch(activity);
}
// install
public static void install(Application application, RefWatcher refWatcher) {
(new ActivityRefWatcher(application, refWatcher)).watchActivities();
}
// 构造函数
public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = (Application)Preconditions.checkNotNull(application, "application");
this.refWatcher = (RefWatcher)Preconditions.checkNotNull(refWatcher, "refWatcher");
}
public void watchActivities() {
this.stopWatchingActivities();
// 为application注册ActivityLiftcycleCallbacks,监听应用内所有Activity的生命周期
this.application.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
}
public void stopWatchingActivities() {
// 为application取消注册ActivityLiftcycleCallbacks监听
this.application.unregisterActivityLifecycleCallbacks(this.lifecycleCallbacks);
}
}
上面的代码逻辑其实很简单,就是为application注册ActivityLifecycleCallback,监听应用内所有Activity的生命周期,LeakCanary只关注onDestroy()
,在Activity的onDestroy()
回调时去做一些事情。
3.2 LeakCanary检测是否发生了内存泄漏
当Activity回调了生命周期中的
onDestory()
时,LeakCanary就运行下面的代码检测是否发生内存泄漏
public final class RefWatcher{
private final Set<String> retainedKeys;
// 引用队列
private final ReferenceQueue<Object> queue;
public void watch(Object watchedReference) {
this.watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if(this != DISABLED) {
// 判空,如果是null的话抛出NullPointerException异常
Preconditions.checkNotNull(watchedReference, "watchedReference");
Preconditions.checkNotNull(referenceName, "referenceName");
// 得到当前时间:作为监视开始时间
long watchStartNanoTime = System.nanoTime();
// 随机生成UUID,并添加到Set<String>中
String key = UUID.randomUUID().toString();
this.retainedKeys.add(key);
// 传入引用队列:监听GC后的情况
KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
// 这个方法是LeakCanary检测对象是否被回收的核心代码
this.ensureGoneAsync(watchStartNanoTime, reference);
}
}
}
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
/**
* @param referent :当前被监视的引用
* @param key:当前被监视对象的key(唯一UUID)
* @param name:引用name
* @param referenceQueue:引用队列
*/
KeyedWeakReference(Object referent, String key, String name, ReferenceQueue<Object> referenceQueue) {
super(Preconditions.checkNotNull(referent, "referent"), (ReferenceQueue)Preconditions.checkNotNull(referenceQueue, "referenceQueue"));
this.key = (String)Preconditions.checkNotNull(key, "key");
this.name = (String)Preconditions.checkNotNull(name, "name");
}
}
/**
* 弱引用
*/
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
* 弱引用可以和引用队列结合起来使用
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
首先:
- 将被监视的对象用弱引用
KeyedWeakReference
包装起来,包括
-
String key
:唯一的UUID -
String name
:被监视的对象的referenceName -
Object reference
:被监视的对象 -
ReferenceQueue<Object> queue
:引用队列
- 将该引用对应的唯一的UUID放入
Set<String> retainedKey
中
然后:再调用ensureGoneAsync
检查是否发生内存泄漏
public final class RefWatcher{
// 真正实现WatchExecutor接口的是AndroidWatchExecutor类,
// 在这个类里面给主线程的消息队列添加一个IdleHandler,
// 当主线程空闲没有消息的时候就执行execute()方法,
// 而在上面的execute()方法里面则调用ensureGone()方法。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
this.watchExecutor.execute(new Retryable() {
public Result run() {
return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
}
});
}
Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 将引用尝试从queue中poll出来
this.removeWeaklyReachableReferences();
if(this.debuggerControl.isDebuggerAttached()) {
// 规避调试模式
return Result.RETRY;
} else if(this.gone(reference)) {
// 检测引用是否已经被回收
return Result.DONE;
} else {
// 手动GC
this.gcTrigger.runGc();
// 再次尝试poll,检测是否被回收
this.removeWeaklyReachableReferences();
if(!this.gone(reference)) {
// 还没有被回收,则dump堆信息,调起分析进程分析
long startDumpHeap = System.nanoTime();
long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = this.heapDumper.dumpHeap();
if(heapDumpFile == HeapDumper.RETRY_LATER) {
return Result.RETRY;
}
long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
}
return Result.DONE;
}
}
private boolean gone(KeyedWeakReference reference) {
return !this.retainedKeys.contains(reference.key);
}
// 检查队列中是否已包含该object的弱引用对象
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
// 引用从引用队列中出队的同时,也将retainedKeys中引用对应的key值移除
this.retainedKeys.remove(ref.key);
}
}
}
需要注意的点:
1. 真正实现WatchExecutor接口的是AndroidWatchExecutor类,执行WatchExecutor的execute()方法
上面代码执行WatchExecutor的execute()方法,真正实现WatchExecutor接口的是AndroidWatchExecutor类,在这个类里面给主线程的消息队列添加一个IdleHandler,当主线程空闲没有消息的时候就执行execute()方法,而在上面的execute()方法里面则调用ensureGone()方法。
2. 如何判断该对象是否被回收
通过判断retainedKey中是否存在该引用对应的key。不存在,说明该对象已被回收【因为在Activity回调onDestroy()时,LeakCanary将该对象的keyUUID添加到了retainedKey中,在removeWeaklyReachableReferences()
方法中尝试将引用队列中的所有引用出队,同时把retainedKey中对应的key移除】
3. 当判断发生内存泄漏后所做的操作
dump heap并生成一个hprof文件并解析这个文件,得到泄漏对象到GC根的最短路径,得到这个路径后发一个Notification展示出来即可。
未完待续............
四、LeakCanary工作机制总结
1.RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2.然后在后台线程检查引用是否被清除,如果没有,调用GC。
3.如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4.在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5.得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6.HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7.引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。
参考文献
Java内存问题 及 LeakCanary 原理分析
利用 LeakCanary 来检查 Android 内存泄漏
三叶梧桐_LeakCanary原理详解
校招指南
网友评论