一、面试中的问题

一般的中高级面试,都会问到性能优化,内存优化问题,而说到内存问题就肯定会问到内存泄漏问题,而一般的求职者二话不说,直接就上LeakCanary

紧接着肯定是问:那你知道LeakCanary的原理是什么吗?
可能还会问:你知道LeakCanary使用到的Idle机制吗?

二、分析LeakCanary原理

LeakCanary的集成非常简单,添加依赖,然后在Application主要是LeakCanary.install(this);
这一句代码,不明白的看文档
LeakCanary

那接下来我们直接看install方法干了什么。

  public static RefWatcher install(Application application) {return refWatcher(application).listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();}

直接看最后的 .buildAndInstall(),发现 install 就是构建了一个 RefWatcher 。

  public RefWatcher buildAndInstall() {...RefWatcher refWatcher = build();if (refWatcher != DISABLED) {if (watchActivities) {// 检测Activity内存泄漏的方式ActivityRefWatcher.install(context, refWatcher);}if (watchFragments) {// 检测Fragment内存泄漏的方式FragmentRefWatcher.Helper.install(context, refWatcher);}}LeakCanaryInternals.installedRefWatcher = refWatcher;return refWatcher;}

我们选择检测Activity内存泄漏的例子,看下对应处理 ActivityRefWatcher.install(context, refWatcher);

  public static void install(Context context, RefWatcher refWatcher) {Application application = (Application) context.getApplicationContext();// 创建一个 ActivityRefWatcherActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);// 从application的registerActivityLifecycleCallbacks接口中,获取到Activity的生命周期回调,并传给 refWatcher。application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);}private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =new ActivityLifecycleCallbacksAdapter() {@Override public void onActivityDestroyed(Activity activity) {refWatcher.watch(activity);}};

application.registerActivityLifecycleCallbacks 这里就是重点了,监听Activity生命周期,然后在 onActivityDestroyed 回调中调用 refWatcher.watch(activity)。

然后继续跟:

private final Set<String> retainedKeys;
private final ReferenceQueue<Object> queue;
...
public void watch(Object watchedReference) {watch(watchedReference, "");}public void watch(Object watchedReference, String 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);}

watchedReference 是传过来的 activity
retainedKeys 是一个Set,用来记录每一个加入检测的对象的key
queue :ReferenceQueue<Object> 引用队列
KeyedWeakReference 继承 WeakReference,保存key和 name,
name传的是空字符串符,可以忽略。

我们来看下 KeyedWeakReference :

final class KeyedWeakReference extends WeakReference<Object> {public final String key;public final String name;KeyedWeakReference(Object referent, String key, String name,ReferenceQueue<Object> referenceQueue) {super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));this.key = checkNotNull(key, "key");this.name = checkNotNull(name, "name");}
}

这里有一个重要知识点,很多文章都没有说到:

弱引用和引用队列搭配使用,如果弱引用持有的对象被回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。也就是说如果KeyedWeakReference 持有的 Activity 对象被回收,该KeyedWeakReference就会加入到引用队列 queue 中。反过来说,就是如果引用队列 queue 中包含该KeyedWeakReference,则表示 KeyedWeakReference 持有的对象已经被回收了。

LeakCanary 就是利用这个原理。

然后呢,创建了弱引用之后,就调用了 ensureGoneAsync()方法。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {watchExecutor.execute(new Retryable() {@Override public Retryable.Result run() {return ensureGone(reference, watchStartNanoTime);}});}

这个 watchExecutor 的实现类是 AndroidWatchExecutor,看看AndroidWatchExecutor#execute方法:

@Override public void execute(Retryable retryable) {if (Looper.getMainLooper().getThread() == Thread.currentThread()) {//主线程中执行 ensureGone() 任务waitForIdle(retryable, 0);} else {//子线程中执行 ensureGone() 任务postWaitForIdle(retryable, 0);}}

为什么不直接分析 ensureGone 方法,因为这里有个小知识点,看 waitForIdle() 方法:

private void waitForIdle(final Retryable retryable, final int failedAttempts) {// 下面的代码,需要在主线程中调用Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Override public boolean queueIdle() {//延时任务postToBackgroundWithDelay(retryable, failedAttempts);return false;}});}

Looper.myQueue().addIdleHandler 会将一个任务添加到主线程消息队列的一个mIdleHandlers列表里,handler在消息队列中取不到消息时,也就是Handler空闲的时候,会去mIdleHandlers列表里取出任务执行。

主线程空闲时候执行这个任务,具体干了什么呢?
postToBackgroundWithDelay,顾名思义,后台延时处理:

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);long delayMillis = initialDelayMillis * exponentialBackoffFactor;backgroundHandler.postDelayed(new Runnable() {@Override public void run() {Retryable.Result result = retryable.run();if (result == RETRY) {postWaitForIdle(retryable, failedAttempts + 1);}}}, delayMillis);}

这里的延时时间delayMillis跟过去是一个常量,5秒
也就是主线程空闲5秒后在后台线程执行 ensureGone() 方法。

重点来了,接下来我们来看一下 ensureGone()方法:

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {long gcStartNanoTime = System.nanoTime();long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);//注释1, 移除弱引用 并移除 Set中对应的 keyremoveWeaklyReachableReferences();//注释2,为true表示 reference 对应的key已经不存在了,即reference被成功回收了,也即没有发生泄露if (gone(reference)) {return DONE;}//注释3,否则 gone(reference) == false,表示 reference 对应的key还存在,即reference还没有被回收,可能是垃圾回收器没有及时回收,手动触发GcgcTrigger.runGc();// 继续移除引用removeWeaklyReachableReferences();//注释4,如果此时 gone(reference) == false,表示 reference 还没有被回收,那就是内存泄漏了。if (!gone(reference)) {long startDumpHeap = System.nanoTime();long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);File heapDumpFile = heapDumper.dumpHeap();if (heapDumpFile == RETRY_LATER) {// Could not dump the heap.return RETRY;}long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);// 注释5,接下来得到一个内存快照,并在 leakCanary 进程中,通过 第三方库 HAHA ,并结合 GC root,对得到的内存快照 .hprof 进行分析,最终得出导致内存泄漏的引用链。HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key).referenceName(reference.name).watchDurationMs(watchDurationMs).gcDurationMs(gcDurationMs).heapDumpDurationMs(heapDumpDurationMs).build();heapdumpListener.analyze(heapDump);}return DONE;}

如何判断内存泄漏的几个步骤

1. removeWeaklyReachableReferences

private void removeWeaklyReachableReferences() {KeyedWeakReference ref;// 当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。// 如果引用队列中是空的,没有Activity对象被回收。// 如果引用队列不为空,则清空引用队列while ((ref = (KeyedWeakReference) queue.poll()) != null) {retainedKeys.remove(ref.key);}}

遍历引用队列,如果queue里面不为空,说明activity被回收了,并在retainedKeys中移除对应的key(引用被回收,则移除key);反之,queue为空,则说明没有引用被回收。

2. gone(reference)

private boolean gone(KeyedWeakReference reference) {return !retainedKeys.contains(reference.key);
}

上面说了“引用被回收则移除key”,那么如果retainedKeys中有对应的key,就说明对象没有被回收。

3. gcTrigger.runGc();
上面分析过 ensureGone 方法是在Activity退出后,主线程空闲时,再过5秒才执行,所以对象没有被回收,不一定就是内存泄漏,对象此时可能已经没有被引用了,正在等待下一次垃圾回收,所以手动触发GC,然后再重复 1和2 的操作,如果对象仍然没被回收,说明真的内存泄漏了。

4. 判断内存泄漏后的处理
if (!gone(reference)) {...}
dump 出堆中的对象,用到HAHA这个库,采用可达性分析算法啥的,包括分析到哪个对象引起的内存泄漏,弹出通知,这些就不是本篇重点了,大家有兴趣可以自己去看。

5. 接下来得到一个内存快照,并在 leakCanary 进程中,通过 第三方库 HAHA ,并结合 GC root,对得到的内存快照 .hprof 进行分析,最终得出导致内存泄漏的引用链。

总结,回答面试中的问题:

LeakCanary的原理是什么?(针对Activity来说)

LeakCanary 通过监听Activity生命周期,在Activity onDestroy的时候,创建一个弱引用,key跟当前Activity绑定,将key保存到set里面,并且关联一个引用队列,然后在主线程空闲5秒后,开始检测是否内存泄漏,具体检测步骤:
1:判断引用队列中是否有该Activity的引用,有则说明Activity被回收了,移除Set里面对应的key。
2:判断Set里面是否有当前要检测的Activity的key,如果没有,说明Activity对象已经被回收了,没有内存泄漏。如果有,只能说明Activity对象还没有被回收,可能此时已经没有被引用,不一定是内存泄漏。
3:手动触发GC,然后重复1和2操作,确定一下是不是真的内存泄漏。

你知道LeakCanary中的Idle机制吗?

在Activity onDestroy的时候,LeakCanary并没有马上去执行检测任务,而是将任务添加到消息队列的一个idle任务列表里,然后当Handler 在消息队列中获取不到消息,也就是主线程空闲的时候,会去idle任务列表里取任务出来执行。

leakCanary原理相关推荐

  1. Leakcanary原理解析以及换肤框架skin的原理分析

    一.错误现场 java.lang.ClassCastException: androidx.appcompat.widget.TintContextWrapper cannot be cast to ...

  2. LeakCanary 原理分析

    LeakCanary 以1.5版本为例子,简单分析其中的原理. LeakCanary 可以检测App的内存泄漏,在我们自定义的 Application 的 onCreate() 方法中执行  Leak ...

  3. LeakCanary 原理浅析

    前言 提到Java语言的特点,无论是教科书还是程序员一般都会罗列出面向对象.可移植性及安全等特点.但如果你是一位刚从C/C++转到Java的程序员,对Java语言的特性除了面向对象之外,最外直接的应当 ...

  4. Android LeakCanary原理分析

    概述 在上一篇LeakCanary使用详细教程中,我们熟悉了LeakCanary的使用和初步描述了它的工作机制,这篇我准备从源码的角度去分析LeakCanary的工作原理: 源码分析 从上一篇中我们知 ...

  5. LeakCanary原理解析

    简介 LeakCanary是一款开源的内存泄漏检查工具,在项目中,可以使用它来检测Activity是否能够被GC及时回收. 使用方式解析 LeakCanary.install()方法的调用流程如下所示 ...

  6. Leakcanary原理笔记

    LeakCanary,Canary金丝雀 很脆弱 常用在矿井里作为预警,一旦死亡,瓦斯泄漏. 使用弱引用+引用队列的形式. 1.监听Activity生命周期 2.在Activity执行onDestor ...

  7. 内存泄漏分析框架LeakCanary的使用与原理解析

    文章目录 1. 常见内存泄漏 1.1 "单例模式" 造成的内存泄漏 1.2 "静态实例" 造成内存泄漏 1.3 "Handler" 造成的内 ...

  8. Android LeakCanary使用详细教程

    导语 在Android的性能优化中,内存优化是必不可少的点,而内存优化最重要的一点就是解决内存泄漏的问题,在Android的内存泄漏分析工具也不少,比如PC端的有:AndroidStudio自带的An ...

  9. Android知识点原理总结

    Activity 4种启动模式 要讲启动模式,先讲讲任务栈Task,它是一种用来放置Activity实例的容器,他是以栈的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈和出栈,其所存放的 ...

最新文章

  1. .NET 反编译调试神器:dnSpy了解一下
  2. Java虚拟机是如何执行线程同步的
  3. AEAP的完整形式是什么?
  4. 独立软件测试团队在敏捷开发中的几个特别实践
  5. Spring 通过XML配置装配Bean
  6. 用python设计图案_用 Python 打造属于自己的GUI图形化界面
  7. 基于hadoop的气象数据可视化分析
  8. android 播放语音anr,Android语音合成出现ANR
  9. JetBrain Clion下载安装及配置环境
  10. 动态内存分配实现冒泡排序
  11. iOS接入Google登录
  12. 手把手教linux设备驱动,手把手教你写Linux设备驱动---定时器(一)(基于友善之臂4412开发板)...
  13. sql计算上班总时长——以每天的第一次上下班打卡时间为准
  14. HTML 图片鼠标悬停动态效果
  15. NIST 网络安全框架导读
  16. 函数柯里化(curry)
  17. 高尔夫热潮四月优雅袭卷鹏城,深圳值得期待的运动类别盛会
  18. ulr实现web资源下载
  19. [ASP.NET]DataBinder.Eval用法,文本绑定
  20. 细节揭秘:淘宝卖汉服,一次预售,卖爆100万销售额!零库存!

热门文章

  1. python tkinter库引用tkk_Python之tkinter:调用python库的tkinter带你进入GUI世界(二)——Jason niu...
  2. WHUT(大学语文)1000分钟,脚本挂刷方法
  3. Exp6 信息搜集与漏洞扫描 20164303
  4. Android仿IOS吸边弹簧阻尼移动组件SpringMovingView-自定义view系列(3)
  5. 华为企业园区网络建设技术方案建议书
  6. 中国商业油库及码头行业运行动态分析及投资规划建议研究报告2022年版
  7. PHP经典乱码“锘”字与解决办法
  8. 智能地址填写功能html代码,JS寄快递地址智能解析的实现代码
  9. C语言,实现字符的渐变,动态化。
  10. 史蒂夫-乔布斯十大励志名言