开始使用

目前为止最新的版本是2.3版本,相比于2.0之前的版本,2.0之后的版本在使用上简洁了很多,只需要在dependencies中加入LeakCanary的依赖即可。而且debugImplementation只在debug模式下有效,所以不用担心用户在正式环境下也会出现LeakCanary收集。

dependencies {

// debugImplementation because LeakCanary should only run in debug builds.

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'

}

在项目中加入LeakCanary之后就可以开始检测项目的内存泄露了,把项目运行起来之后, 开始随便点自己的项目,下面以一个Demo项目为例,来聊一下LeakCanary记录内存泄露的过程以及我如何解决内存泄露的。

项目运行起来之后,在控制台可以看到LeakCanary的打印信息:

D/LeakCanary: Check for retained object found no objects remaining

D/LeakCanary: Scheduling check for retained objects in 5000ms because app became invisible

D/LeakCanary: Check for retained object found no objects remaining

D/LeakCanary: Rescheduling check for retained objects in 2000ms because found only 3 retained objects (< 5 while app visible)

这说明LeakCanary正在不断的检测项目中是否有剩余对象。那么LeakCanary是如何工作的呢?LeakCanary的基础是一个叫做ObjectWatcher Android的library。它hook了Android的生命周期,当activity和fragment 被销毁并且应该被垃圾回收时候自动检测。这些被销毁的对象被传递给ObjectWatcher, ObjectWatcher持有这些被销毁对象的弱引用(weak references)。如果弱引用在等待5秒钟并运行垃圾收集器后仍未被清除,那么被观察的对象就被认为是保留的(retained,在生命周期结束后仍然保留),并存在潜在的泄漏。LeakCanary会在Logcat中输出这些日志。

D LeakCanary: Watching instance of com.example.leakcanary.MainActivity

// 5 seconds later...

D LeakCanary: Found 1 retained object

在我一顿瞎点之后, 在手机通知栏上面出现了这样的提示:

image.png上面的意思是已经发现了4个保留的对象,点击通知可以触发堆转储(dump heap)。在app可见的时候,会一直等到5个保留的对象才会触发堆转储。这里要补充的一点是:当应用可见的时候默认的阈值是5,应用不可见的时候阈值是1。如果你看到了保留的对象的通知然后将应用切换到后台(例如点击home键),那么阈值就会从5变到1,LeakCanary会立即进行堆转储。那么堆转储是什么一回事呢?

堆转储

在我点了上面的通知之后, 控制台打印出了下面的语句:

D/LeakCanary: Check for retained objects found 3 objects, dumping the heap

D/LeakCanary: WRITE_EXTERNAL_STORAGE permission not granted, ignoring

I/testapplicatio: hprof: heap dump "/data/user/0/com.example.leakcaneraytestapplication/files/leakcanary/2020-05-28_16-35-28_155.hprof" starting...

I/testapplicatio: hprof: heap dump completed (22MB) in 2.963s objects 374548 objects with stack traces 0

这里开始进行堆转储,同时生成.hprof文件,LeakCanary将java heap的信息存到该文件中。同时在应用程序中也会出现一个提示。

image.png

LeakCanary是使用shark来转换.hprof文件并定位Java堆中保留的对象。如果找不到保留的对象,那么它们很可能在堆转储的过程中回收了。

image.png对于每个被保留的对象,LeakCanary会找出阻止该保留对象被回收的引用链:泄漏路径。泄露路径就是从GC ROOTS到保留对象的最短的强引用路径的别名。确定泄漏路径以后,LeakCanary使用它对Android框架的了解来找出在泄漏路径上是谁泄漏了。

解决内存泄露

打开生成的Leaks应用,界面就类似下面这样婶儿滴。LeakCanary会计算一个泄漏路径并在UI上展示出来。这就是LeakCanary很友好的地方,通过UI展示,可以很直接的看到内存泄漏的过程。相对于mat和android studio 自带的profiler分析工具,这个简直太直观清晰了!

image.png

同时泄漏路径也在logcat中展示了出来:

HEAP ANALYSIS RESULT

====================================

1 APPLICATION LEAKS

References underlined with "~~~" are likely causes.

Learn more at https://squ.re/leaks.

111729 bytes retained by leaking objects

Signature: e030ebe81011d69c7a43074e799951b65ea73a

┬───

│ GC Root: Local variable in native code

├─ android.os.HandlerThread instance

│ Leaking: NO (PathClassLoader↓ is not leaking)

│ Thread name: 'LeakCanary-Heap-Dump'

│ ↓ HandlerThread.contextClassLoader

├─ dalvik.system.PathClassLoader instance

│ Leaking: NO (ToastUtil↓ is not leaking and A ClassLoader is never leaking)

│ ↓ PathClassLoader.runtimeInternalObjects

├─ java.lang.Object[] array

│ Leaking: NO (ToastUtil↓ is not leaking)

│ ↓ Object[].[871]

├─ com.example.leakcaneraytestapplication.ToastUtil class

│ Leaking: NO (a class is never leaking)

│ ↓ static ToastUtil.mToast

│ ~~~~~~

├─ android.widget.Toast instance

│ Leaking: YES (This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null))

│ ↓ Toast.mContext

╰→ com.example.leakcaneraytestapplication.LeakActivity instance

​ Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

​ key = c1de58ad-30d8-444c-8a40-16a3813f3593

​ watchDurationMillis = 40541

​ retainedDurationMillis = 35535

====================================

0 LIBRARY LEAKS

路径中的每一个节点都对应着一个java对象。熟悉java内存回收机制的同学都应该知道”可达性分析算法“,LeakCanary就是用可达性分析算法,从GC ROOTS向下搜索,一直去找引用链,如果某一个对象跟GC Roots没有任何引用链相连时,就证明对象是”不可达“的,可以被回收。

我们从上往下看:

GC Root: Local variable in native code

在泄漏路径的顶部是GC Root。GC Root是一些总是可达的特殊对象。

接着是:

├─ android.os.HandlerThread instance

│ Leaking: NO (PathClassLoader↓ is not leaking)

│ Thread name: 'LeakCanary-Heap-Dump'

│ ↓ HandlerThread.contextClassLoader

这里先看一下Leaking的状态(YES、NO、UNKNOWN),NO表示没泄露。那我们还得接着向下看。

├─ dalvik.system.PathClassLoader instance

│ Leaking: NO (ToastUtil↓ is not leaking and A ClassLoader is never leaking)

│ ↓ PathClassLoader.runtimeInternalObjects

上面的节点告诉我们Leaking的状态还是NO,那再往下看。

├─ android.widget.Toast instance

│ Leaking: YES (This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null))

│ ↓ Toast.mContext

中间Leaking是NO状态的我就不再贴出来,我们看看Leaking是YES的这一条,这里说明发生了内存泄露。

”This toast is done showing (Toast.mTN.mWM != null && Toast.mTN.mView == null)“,这里说明Toast发生了泄露,android.widget.Toast 这是系统的Toast控件,这说明我们在使用Toast的过程中极有可能创建了Toast对象,但是该回收它的时候无法回收它,导致出现了内存泄露,这里我们再往下看:

╰→ com.example.leakcaneraytestapplication.LeakActivity instance

​ Leaking: YES (ObjectWatcher was watching this because com.example.leakcaneraytestapplication.LeakActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)

这里就很明显的指出了内存泄露是发生在了那个activity里面,我们根据上面的提示,找到对应的activity,然后发现了一段跟Toast有关的代码:

image.png

这里再进入ToastUtil这个自定义Toast类里面,看看下面的代码,有没有发现什么问题?这里定义了一个static的Toast对象类型,然后在showToast的时候创建了对象,之后就没有然后了。我们要知道static的生命周期是存在于整个应用期间的,而一般Toast对象只需要显示那么几秒钟就可以了,因为这里创建一个静态的Toast,用完之后又没有销毁掉,所以这里提示有内存泄露了。因此我们这里要么不用static修饰,要么在用完之后把Toast置为null。

public class ToastUtil {

private static Toast mToast;

public static void showToast(Context context, int resId) {

String text = context.getString(resId);

showToast(context, text);

}

public static void showToast(Context context, String text){

showToast(context, text, Gravity.BOTTOM);

}

public static void showToastCenter(Context context, String text){

showToast(context, text, Gravity.CENTER);

}

public static void showToast(Context context, String text, int gravity){

cancelToast();

if (context != null){

LayoutInflater inflater = (LayoutInflater) context

.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View layout = inflater.inflate(R.layout.toast_layout, null);

((TextView) layout.findViewById(R.id.tv_toast_text)).setText(text);

mToast = new Toast(context);

mToast.setView(layout);

mToast.setGravity(gravity, 0, 20);

mToast.setDuration(Toast.LENGTH_LONG);

mToast.show();

}

}

public static void cancelToast() {

if (mToast != null){

mToast.cancel();

}

}

}

讲了这么多,其实内存泄露的本质是长周期对象持有了短周期对象的引用,导致短周期对象该被回收的时候无法被回收,从而导致内存泄露。我们只要顺着LeakCaneray的给出的引用链一个个的往下找,找到发生内存泄露的地方,切断引用链就可以释放内存了。

这里再补充一点上面的这个例子里面Leaking没有UNKNOWN的状态,一般情况下除了YES、NO还会出现UNKNOWN的状态,UNKNOWN表示这里可能出现了内存泄露,这些引用你需要花时间来调查一下,看看是哪里出了问题。一般推断内存泄露是从最后一个没有泄漏的节点(Leaking: NO )到第一个泄漏的节点(Leaking: YES)之间的引用。

android内存泄漏检测,Android内存泄露检测之LeakCanary的使用相关推荐

  1. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  2. java内存泄漏怎么检测_JAVA内存泄漏原因和内存泄漏检测工具

    JAVA内存泄漏原因和内存泄漏检测工具 摘要 虽然Java 虚拟机(JVM)及其垃圾收 集器(garbage collector,GC)负责管理大多数的内存任务,Java 软件程序中还是有可能出现内 ...

  3. c和java内存泄漏区别_内存溢出和内存泄漏的区别(转)

    1.1内存溢出:(Out Of Memory---OOM) 系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩90M了,这就叫内存溢出 例子:一个盘子用尽各种方法只能装4个果子,你 ...

  4. 常规循环引用内存泄漏和Closure内存泄漏

    常规循环引用内存泄漏和Closure内存泄漏 要了解javascript的内存泄漏问题,首先要了解的就是javascript的GC原理. 我记得原来在犀牛书<JavaScript: The De ...

  5. Detected memory leaks!内存泄漏,溢出,内存越界问题分析

    应用程序发生 Detected memory leaks!内存泄漏 一直程序员面对的是一个很痛苦的问题,要查出泄漏的地方有时候需要大半天甚至更长时间.这里讲讲我的一些查找内存泄漏以及避免内存泄漏的一些 ...

  6. 【内存泄漏】- 3. 使用splint检测c、c++内存泄漏

    在上一篇博文中已经介绍过什么是静态内存分析,静态内存分析可以检测程序的哪些逻辑问题.上篇博文主要讲解cppcheck工具在linux下的使用,这篇博文主要讲解splint工具可以检测哪些代码逻辑,与c ...

  7. 哪些操作会造成内存泄漏及Js内存泄露解决方法

    1.垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量.如果一个对象的 引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的 内存即可回收 2.setTim ...

  8. linux如何定位内存泄漏,快速定位内存泄漏的套路(linux)

    快速定位内存泄漏的套路(linux) 快速定位内存泄漏的套路(linux) https://blog.csdn.net/xieyihua1994/article/details/105248362/ ...

  9. python会内存泄漏吗_Python内存泄漏和内存溢出的解决方法

    Python内存泄漏和内存溢出的解决方法 发布时间:2020-10-30 23:08:34 来源:亿速云 阅读:92 作者:Leah 这篇文章将为大家详细讲解有关Python内存泄漏和内存溢出的解决方 ...

  10. python内存泄漏解决方案_Python内存泄漏和内存溢出的解决方案

    一.内存泄漏 像Java程序一样,虽然Python本身也有垃圾回收的功能,但是同样也会产生内存泄漏的问题. 对于一个用 python 实现的,长期运行的后台服务进程来说,如果内存持续增长,那么很可能是 ...

最新文章

  1. java char的包装对象,Java 从Character和char的区别来学习自动拆箱装箱
  2. [云炬创业基础笔记]第十章企业的利润计划测试8
  3. php7.0 百度百科,PHP 7.0.10正式发布
  4. PHP闭包(Closure)初探(转载 http://my.oschina.net/melonol/blog/126694?p=2#comments)
  5. plsql能连mysql吗_面试官:能给我讲讲用代码实现MySQL的读写分离的思路吗?
  6. cocos2d-x CCArray用法 遍历和删除元素
  7. 在线凯撒密码自动分析工具
  8. java web邮件收发组件
  9. TD-SCDMA迫零块线性均衡
  10. 其实IPv6,并不是那么完美
  11. Composer的基本使用
  12. 【数字IC验证快速入门】45、UVM项目实践之APB_SPI(13)UVM 验证方法学总结
  13. 数据科学家为什么还要学藏语?这不科学。首份藏文数字数据集出炉
  14. 苹果不创新,库克有道理
  15. 【排序】详细聊聊归并排序(含非递归)
  16. 分布式、服务化的企业级 ERP 系统架构设计方案
  17. 《移动游戏开发精要》读书笔记
  18. 创造与魔法java语言_《创造与魔法》食谱大全
  19. 计算机毕业设计Java学生考勤管理系统(源码+系统+mysql数据库+lw文档
  20. [问题解决] 西部数据硬盘 mac 无法写入

热门文章

  1. php nodejs 慢多少,node.js,_nodejs前端频繁post 导致很卡的问题,node.js - phpStudy
  2. windows7 安装.net4/4.5 Framework 最简单方法
  3. java properties api_Java Properties 接口 - Java 教程 - 自强学堂
  4. MongoDB小结09 - update【定位修改器】
  5. 三星电子 CEO 敲定,三人联席架构继续,李尚勋将任董事会主席
  6. android开发相关资源
  7. 关于高速光耦6n137的使用总结_今天用ADUM1201数字隔离器件和高速光耦6N137对8M的信号进行隔离输出...
  8. php爬虫全国地址信息
  9. OpenCV每日函数 计算摄影模块(1) 图像修复算法 inpaint函数
  10. 笔记本越用越慢的解决方法。