1.LeakCanary
LeakCanary是一个自动检测内存泄漏的工具。一旦检测到内存泄漏,LeakCanary就会dump内存信息,并通过另一个进程分析内存泄漏的信息并展示出来,可以随时发现和定位内存泄漏问题,极大地方便了Android应用程序的开发。
LeakCanary优点:
①针对Activity/Fragment组件完全自动化的内存泄漏检查。
②可定制一些行为(dump文件和leaktrace对象的数量、自定义例外、分析结果的自定义处理等)。
③集成到自己工程,使用的成本很低。
④友好的界面展示和通知。
LeakCanary工作机制:
①RefWatcher.watch()创建一个KeyedWeakReference到要被监控的对象。
②然后在后台线程检查引用是否被清除,如果没有,调用GC。
③如果引用还是未被清除,把heap内存dump到APP对应的文件系统中的一个.hprof文件中。
④在另外一个进程中的HeapAnalyzerService有一个HeapAnalyzer使用HAHA解析这个文件。
⑤得益于唯一的reference key,,HeapAnalyzer找到KeyedWeakReference,定位内存泄露。
⑥HeapAnalyzer计算到GC roots的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
⑦引用链传递到APP进程中的DisplayLeakService , 并以通知的形式展示出来。
2.LeakCanary的用法(2.0以前的版本)
①在build.gradle中添加LeakCanary的依赖包。
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
在开发中一般同时集成debug和release版本,其中:
com.squareup.leakcanary:leakcanary-android:1.6.1 是debug版本,当app编译的是debug版本时,加载的是该jar包,一旦出现内存泄漏会在通知栏中通知开发者产生了内存泄漏;
com.squareup.leakcanary:leakcanary-android-no-op:1.6.1 是release版本,如果app编译的是release版本时,加载的是该jar包,no-op是指No Operation Performed,代表不会做任何操作,不会干扰正式用户的使用;
②在自定义Application的onCreate方法中注册LeakCanary。
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
解释一下为什么要先判断LeakCanary.isInAnalyzerProcess(this):在注册之前先判断LeakCanary是否已经运行在手机上,比如同时有多个APP集成了LeakCanary,其他app已经运行了LeakCanary,则不需要重新install。
看一下isInAnalyzerProcess方法的源码:
public static boolean isInAnalyzerProcess( Context context) {
Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
if (isInAnalyzerProcess == null) {
isInAnalyzerProcess = isInServiceProcess( context, HeapAnalyzerService.class);
LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
}
return isInAnalyzerProcess;
}
从源码中可以看到真正调用的是isInServiceProcess方法,看isInServiceProcess的源码:
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass){
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());
return false;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (PackageManager.NameNotFoun dException ignored) {
return false;
}
if (serviceInfo.processName.equals( mainProcess)) {
CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager = (ActivityManager) context.getSystemService( Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessIn fo> runningProcesses;
try {
runningProcesses = activityManager.getRunningAppProcesses();
} catch (SecurityException exception) {
CanaryLog.d("Could not get running app processes %d", exception);
return false;
}
if (runningProcesses != null) {
for (ActivityManager.RunningAppProce ssInfo process : runningProcesses) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d("Could not find running process for %d", myPid);
return false;
}
return myProcess.processName.equals( serviceInfo.processName);
}
可以看到ComponentName component = new ComponentName(context, serviceClass);而这个参数serviceClass是isInAnalyzerProcess方法中传入的HeapAnalyzerService,HeapAnalyzerService正是用来分析内存泄漏的单独进程,所以说LeakCanary在同一个手机只需要执行一次install就可以了,当然执行多次也是可以的。
正常情况下Application调用LeakCanary.install( this)后就可以正常监听该app程序的内存泄漏了。
3.LeakCanary监听指定对象的内存泄漏
如果想让LeakCanary监听指定对象的内存泄漏,就需要使用到RefWatcher的watch功能,使用方式如下:
①在Application的onCreate中调用install方法,并获取RefWatcher对象:
private static RefWatcher sRefWatcher;
@Override
public void onCreate() {
super.onCreate();
sRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher() {
return sRefWatcher;
}
注意:因为这时候需要获取sRefWatcher对象,所以sRefWatcher = LeakCanary.install(this)一定需要执行,不需要判断LeakCanary.isInAnalyzerProcess(this)。
②在需要监听的对象中调用RefWatcher的watch方法进行监听,比如想监听一个Activity,就可以在该Acitivity中onCreate方法中添加DemoApp.getRefWatcher().watch(this);,如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DemoApp.getRefWatcher().watch(this);
setContentView(R.layout.activity_second);
……
}
当app出现内存泄漏后,过一小段时间就会在通知栏中通知出现内存泄漏的情况,同时会在桌面上生成一个Leaks的图标,这个就是展示内存泄漏列表的,可以通过点击进入查看泄漏的内容。
这里总结一下产生内存泄漏的常见场景和常用的解决方案
常见的内存泄漏场景:
①单例模式造成内存泄漏
单例里传入Activity的context会造成内存泄露。解决办法是传入Application的context。
②非静态内部类创建的静态实例造成内存泄漏
public class StaticLeakActivity extends Activity {
private static NoneStaticInnerClass mResource = null;
@override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if(mResource == null) {
mResource = new NoneStaticInnerClass();
}
}
private class NoneStaticInnerClass{
……
}
}
非静态内部类创建的静态实例会造成内存泄漏,解决办法是将这个非静态内部类改成静态内部类,这样就不会持有Activity的引用
③Handler造成的内存泄漏
④线程造成的内存泄漏
⑤资源未关闭造成的内存泄漏
常见的解决方案(思路):
①尽量使用Application的Context而不是Activity的
②使用弱引用或者软引用
③手动设置null,解除引用关系
④将内部类设置为static,不隐式持有外部的实例
⑤注册与反注册成对出现,在对象合适的生命周期进行反注册操作。
⑥如果没有修改的权限,比如系统或者第三方SDK,可以使用反射进行解决持有关系
⑦在使用完BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等资源时,一定要在Activity中的OnDestry中及时的关闭、注销或者释放内存
4.源码分析(2.0以前的版本)
LeakCanary在Appaction的初始化方式:
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
LeakCanary.install(this)调用之后LeakCanary已经可以正常监控应用中所有Activity的内存泄漏情况了,下面从源码的角度来看看实现原理:
LeakCanary.java:
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServi ceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder( context);
}
①install方法的返回值是RefWatcher,这个对象非常重要,LeakCanary就是通过这个对象进行Activity的内存监控。
②RefWatcher是通过Builder模式创建的。
③install方法内部一连串的链式调用,最后调用了buildAndInstall()方法。
AndroidRefWatcherBuilder.java:
private boolean watchActivities = true;
private boolean watchFragments = true;
public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException( "buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();//RefWatch就是在这边创建的
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync( context, DisplayLeakActivity.class, true);
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install( context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
①LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);允许泄漏报告页面的显示;
②watchActivities/watchFragments默认值都是true,所以ActivityRefWatcher和FragmentRefWatcher都将被启用;
③ActivityRefWatcher用于监控Activity的内存泄漏
④FragmentRefWatcher用于监控Fragment的内存泄漏;
接下来通过ActivityRefWatcher进一步分析LeakCanary是如何监控内存泄漏的:
ActivityRefWatcher.java:
public static void install(Context context, RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks( activityRefWatcher.lifecycleCallbacks);
}
①registerActivityLifecycleCallbacks是Android Application的一个方法,注册了该方法,应用中每一个Activity的生命周期变化都会通过该方法回调回来;
②registerActivityLifecycleCallbacks方法传入的参数是activityRefWatcher.lifecycleCallbacks,到ActivityRefWatcher中看下该方法的实现:
ActivityRefWatcher.java:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
可以看到,ActivityRefWatcher只监听了onActivityDestroyed方法,也就是说每一个Activity调用onDestory的时候都会回调到onActivityDestroyed这个方法,通知LeakCanary该Activity应该被销毁。
到这来就明白了为什么只执行LeakCanary.install( this)一条语句就可以完成对整个app的Activity内存泄漏进行监听了。
接下来看看ActivityRefWatcher是如何监听内存泄漏的,RefWatcher有两个核心方法: watch() 和 ensureGone():
RefWatcher.java:
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();
retainedKeys.add(key);
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
①官方对watch方法的注释:监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的。
②checkNotNull:分别检测watchedReference、referenceName是否为空,如果为空则抛出异常结束。
③随机生成一个key,该key用于唯一标识已产生内存泄漏的对象,或者准备检测的对象。
④创建KeyedWeakReference对象,并调用另一个核心方法ensureGone。
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) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
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();
if (heapDumpFile == RETRY_LATER) {
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key).referenceName(reference.name).watchDurationMs(watchDurationMs).gcDurationMs(gcDurationMs).heapDumpDurationMs( heapDumpDurationMs).build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
①ensureGone方法是在ensureGoneAsync方法中开启的,并且是一个异步任务,不会对主线程造成阻塞。
②removeWeaklyReachableReferences():移除已经回收的弱引用对象的key,如果需要判断的对象已经销毁,则不继续执行。
if (gone(reference)) {
return DONE;
}
③如果移除之后还是存在该引用则手动再GC一次:gcTrigger.runGc();
④然后二次调用removeWeaklyReachableReferences()再判断是否已经销毁。
⑤如果二次确认还是存在,则判断为内存泄漏了,开始.hprof文件的dump。
⑥调用heapdumpListener.analyze(heapDump)进行泄漏分析。
总结一下:
①弱引用与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中。通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。
②为了确保最大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在极小程度的误差的。
hprof文件生成好以后,接下来就通过heapdumpListener.analyze(heapDump)进行内存泄漏最短路径分析:
@Override
public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
调用了HeapAnalyzerService,在单独的进程中进行分析。
直接把HeapAnalyzerService整个类的代码贴上:
public final class HeapAnalyzerService extends ForegroundService implements AnalyzerProgressListener {
private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
private static final String HEAPDUMP_EXTRA = "heapdump_extra";
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService( context, intent);
}
public HeapAnalyzerService() {
super(HeapAnalyzerService.class.getSim pleName(),R.string.leak_canary_notification_analysing);
}
@Override
protected void onHandleIntentInForeground( 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, this, heapDump.reachabilityInspectorClasses);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResu ltToListener(this, listenerClassName, heapDump, result);
}
@Override
public void onProgressUpdate(Step step) {
int percent = (int) ((100f * step.ordinal()) / Step.values().length);
CanaryLog.d("Analysis in progress, working on: %s", step.name());
String lowercase = step.name().replace("_", " ").toLowerCase();
String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
showForegroundNotification(100, percent, false, message);
}
}
①runAnalysis方法中最后一句:ContextCompat.startForegroundService(context, intent)启动HeapAnalyzerService。
②HeapAnalyzerService继承自ForegroundService,服务启动时会调用onHandleIntentInForeground方法。
onHandleIntentInForeground方法中比较核心的方法调用是:
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
HeapAnalyzer中核心的方法是checkForLeak,来看下它的具体实现:
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey, boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
listener.onProgressUpdate( READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate( PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate( DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
listener.onProgressUpdate( FINDING_LEAKING_REF);
Instance leakingRef = findLeakingReference( referenceKey, snapshot);
if (leakingRef == null) {
return noLeak(since( analysisStartNanoTime));
}
return findLeakTrace( analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
①官方对该方法的注释:在dump的堆信息中通过key查找出内存泄漏的弱引用对象,并且计算出最短的GC路径。
②Snapshot snapshot = parser.parse()–生成文件的快照。
③deduplicateGcRoots(snapshot)–过滤重复的内存泄漏对象。
④findLeakingReference(referenceKey, snapshot)–在快照中根据referenceKey查找是否有对应的内存泄漏对象,如果获取到的leakingRef为空,则说明内存已经被回收了,不存在内存泄漏的情况;如果leakingRef不为空则进入下一步查找内存泄漏对象的GC最短路径。
⑤findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize)–查找内存泄漏对象的GC最短路径。
LeakCanary的核心源码已经都已经分析完了,剩下的就是如果在页面上显示出来了。
总结一下用到的重要的类和方法:
①LeakCanary#install()
SDK初始化接口, 通常只需要调用该方法就可以监控app的内存泄漏情况。
②DisplayLeakActivity类
内存泄漏的查看显示页面
③RefWatcher#watch() / ensureGone()
watch():监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的。
ensureGone():2次确认是否确实存在内存泄漏,如果存在内存泄漏则dump泄漏信息到hprof文件中,并调用ServiceHeapDumpListener回调进行内存分析。
④ActivityRefWatcher类
Activity引用检测, 包含了Activity生命周期的监听执行与停止。
⑤HeapAnalyzerService#runAnalysis
内存堆分析服务, 为了保证App进程不会因此受影响变慢&内存溢出,运行于独立的进程。
⑥HeapAnalyzer#checkForLeak
在dump的堆信息中通过key查找出内存泄漏的弱引用对象,并且计算出最短的GC路径。
5.LeakCanary2.0以后的版本
LeakCanary2.0版本开始使用kotlin语言,使用起来更加简单,省去了在Application中的注册,只需要在build.gradle文件中加入依赖即可,完全实现了自动化检测。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0'
这样就可以了,代码上什么都不用写,完全做到了无感知操作。
那代码上什么都不用写,不用注册它怎么来进行调用,进行内存监测呢?答案就在AppWatcherInstaller这个类上,它继承了ContentProvider,属于四大组件之一。ContentProvider的特点就是不用显示调用初始化,在执行完application的初始化后就会调用ContentProvider的onCreate()方法。正是利用这一点,leakcanary把注册写在了这里面,由系统自动调用完成,对开发者完全无感知。
leakCananry源码分两部分来看:①添加要观察的对象 ②对对象进行观察。
①第一部分,添加观察对象
在AppWatcherInstaller的onCreate中调用了InternalAppWatcher的install方法
AppWatcherInstaller.java:
internal sealed class AppWatcherInstaller : ContentProvider {
override fun onCreate() : Boolean {
val application = context!!.applicationContext as Application
InternalAppWatcher.install(application)
return true
}
……
}
InternalAppWatcher.java:
fun install(application Application) {
checkMainThread()
if(this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
val configProvider = {AppWatcher.config}
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
}
install方法主要做了两件事情:
1)检查是否在主线程(不是的话抛出异常)
2)添加生命周期的回调,在执行onDestory的时候调用objectWatcher的watch方法,观察这个activity的对象是否回收掉了。
ActivityDestroyWatcher在执行onDestory的时候开始进行对象检测
ActivityDestroyWatcher.java:
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher : ObjectWatcher
private val configProvider : ( ) -> Config
) {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(activity)
}
}
}
companion object {
fun install(application : Application, objectWatcher : ObjectWatcher, configProvider : () -> Config) {
val activityDestroyWatcher = ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifeCycleCallb acks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
以上为注册流程。
②第二部分就是检测对象的回收了
检测对象的回收,起始类是ObjectWatcher,即对象观察器,ObjectWatcher里面有两个集合分别是:
1)private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
这是一个map集合,用来存放要观察对象的key和弱引用,代码会为每个观察的对象生成一个唯一的key和弱引用。
2)private val queue = ReferenceQueue<Any>()
watchedObjects这个队列和弱引用联合使用,当弱引用中的对象被回收后,这个弱引用会被放到这个队列中。也就是说,只要这个队列中存在弱引用,就代表这个弱引用中所包含的对象被回收了。
然后调用ObjectWatcher.watch方法:
ObjectWatcher.watch(watchedObject : Any,description : String)
watch方法里面做了如下操作:
1)先移除watchedObjects和queue集合里面已经回收对象的弱引用。
removeWeaklyReachableObjects()
2)通过uuid为当前观察的对象生成一个唯一的key,并把对象用弱引用包起来,放到watchedObjects这个集合中,同时把queue和弱引用关联起来。
3)执行checkRetainedExecutor.execute {
moveToRetained(key)
}
@Synchronized private fun moveToRetained(key : String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if(retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
这里是个延迟任务,延迟五秒后再次执行1)操作,这个期间对象可能已经被回收了,所以需要再次移除一次。
4)执行了3)操作后,就可以通过watchedObjects 集合找到没有被回收的对象了。这时候就可以获取到没有被回收的对象的个数,大于0,进行一次gc操作(gc操作用的是Runtime.getRuntime() .gc(),源码上注释这个操作比system.gc()更可能触发gc操作)。
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
5)再次获取未被回收的个数,这一步会有几个判断条件:
①如果个数小于5,不做操作等待5秒再次进行检查未回收的个数,一直循环,直到大于等于5个或者等于0个,为了防止频发回收堆造成卡顿。
②大于5个后,如果处于debug模式,会再等20秒,再次执行4操作。防止debug模式会减慢回收
③距离上次堆栈分析是否大于等于1分钟,如果没有超过一分钟,也需要再次延迟(1分钟-当前距离上次的时间)再次循环4操作
6)如果上面的条件都符合了,就可以开始进行堆栈的分析了:
①获取到内容文件 Debug.dumpHprofData(heapDumpFile.absolutePath)
②objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)该操作是去掉以前已经分析过的对象,也就是去除掉之前的没有回收掉的对象,不在本次分析范围内
③HeapAnalyzerService开启IntentService服务进行分析
④把结果插入数据库(泄漏区分了应用本身的内存泄漏和类库的内存泄漏),并且发送通知
6.总结
①LeakCanary主要是通过Application的registerActivityLifecycleCallbacks方法监控每一个Activty的Destory之后对象是否被收回。
②在Activity Destory之后ActivityRefWatch的watch方法将被调用,watch方法会通过一个随机生成的key将这个弱引用关联到一个ReferenceQueue,然后调用ensureGone()。
注:当一个软引用/弱引用对象被垃圾回收后,Java虚拟机就会把这个引用加入到与之关联的引用队列中。
③ActivityRefWatch的ensureGone()方法中会先确认一次是否已经被回收,如果发现没有被回收,则主动GC一下,然后在次确认是否被回收,如果还是没有回收则判断为内存泄漏。
④一旦确认是内存泄漏,则开始dump信息到hprof文件中,并调用heapdumpListener.analyze(heapDump)开始内存分析。
⑤内存分析是在HeapAnalyzerService服务中进行的,属于一个单独的进程。
⑥HeapAnalyzerService的runAnalysis中创建HeapAnalyzer对象并调用它的一个核心方法checkForLeak()。
⑦HeapAnalyzer的checkForLeak()会先解析hprof文件并生成快照文件,然后对快照中的泄漏对象进行去重,去重后根据第②步中的key去获取泄漏对象,如果对象为空则说明对象已经被回收,如果不为空则通过findLeakTrace()方法计算出最短GC路径,并显示到DisplayLeakActivity页面,提醒开发者存在内存泄漏。