LeakCanary 使用及原理分析
文章目录
- 一、基础
- 1、添加依赖
- 2、基本原理
- 2.1 什么是内存泄漏
- 2.2 内存泄漏的常见原因
- 3、为什么要使用LeakCanary
- 4、LeakCanary是怎么工作的
- 4.1 Detecting retained objects 检测保留对象
- 4.2 Dumping the heap 倾倒堆
- 4.3 Analyzing the heap 堆分析
- 4.4 Categorizing leaks 泄漏分类
- 5、修复内存泄漏
- 5.1 找到泄漏问题
- 5.2 缩小可疑引用范围
- 5.3 找到泄漏的引用
- 5.4 修复泄漏问题
- 参考:https://square.github.io/leakcanary/
一、基础
1、添加依赖
dependencies {// debugImplementation because LeakCanary should only run in debug builds.debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
2、基本原理
2.1 什么是内存泄漏
在基于Java的runtime状态下,内存泄漏是一种变成错误,导致应用保留对无用对象引用,从而引起内存无法回收,导致OOM崩溃。
2.2 内存泄漏的常见原因
大多数内存泄漏时由与生命周期相关的错误引起的,常见Android错误:
- Fragment 实例添加到栈中但是没有在onDestoryView()方法中释放
- Activity 实例引用作为context字段引用,导致配置变更时activity重建无法释放
- 注册广播、监听、或者RxJava添加订阅,未根据生命周期做响应的释放
- 数据库查询时cursor使用完毕没有关闭导致
- bitmap使用完毕没有置空释放
- RecyclerView与NestedScrollView嵌套错误导致无法复用释放资源
3、为什么要使用LeakCanary
内存泄漏在Android应用中非常普遍,小的内存泄漏积累最终会导致OOM异常。LeakCanary可以在开发中帮助发现和修复内存泄漏。
4、LeakCanary是怎么工作的
一旦LeakCanary安装后,会自动检测和报告内存泄漏,主要分为4步:
- Detecting retained objects. 检测保留的对象
- Dumping the heap. 倾倒堆
- Analyzing the heap. 分析堆
- Categorizing leaks. 对内存泄漏进行分类
4.1 Detecting retained objects 检测保留对象
- LeakCanary hook了Android生命周期实现自动检测Activity和Fragment的销毁,并进行垃圾收集。这些被销毁的对象被传递给一个ObjectWatcher,它持有对他们的弱引用,LeakCanary自动检测以下对象的泄漏:
- 销毁的Activity实例
- 销毁的Fragment实例
- 销毁的片段View实例
- 清除ViewModel实例
可以通过AppWatcher来对对象的回收过程进行监控
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
如果ObjectWatcher在等待 5 秒并运行垃圾收集后没有清除持有的弱引用,则被监视的对象被认为是保留的,并且可能会泄漏。LeakCanary 将此记录到 Logcat:
D LeakCanary: Watching instance of com.example.leakcanary.MainActivity(Activity received Activity#onDestroy() callback) ... 5 seconds later ...D LeakCanary: Scheduling check for retained objects because found new objectretained
4.2 Dumping the heap 倾倒堆
当持有的对象到达阀值时,LeakCanary会倾倒Java的堆内存到.hprof文件中,并存储在Android文件系统中。在倾倒堆时会造成App短暂停止,LeakCanary会展示一个如下Toast
4.3 Analyzing the heap 堆分析
LeakCanary通过使用Shark库来解析.hprof文件,并定位堆垃圾场中持有的对象
针对每个保留的对象,LeakCanary查询阻止其被垃圾回收的引用路径,进行泄漏跟踪。分析完成后会显示带有摘要的通知,并将结果打印在logcat中。LeakCanary会为每个泄漏trace创建签名,并根据签名对其进行分组
====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKSDisplaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
...
LeakCanary会为每个集成其的app在手机上添加一个启动图标
LeakCanary每行日志对应一组具有相同签名的泄漏。LeakCanary在应用程序第一次使用该签名触发泄漏时将一行标记为new
点击每个泄漏可以通过查看详细信息
4.4 Categorizing leaks 泄漏分类
LeakCanary会将应用中的内存泄漏分为两类:
- 应用程序泄漏
- Library 泄漏
并在logcat打印的日志中分开
====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS====================================
1 LIBRARY LEAK...
┬───
│ GC Root: Local variable in native code
│
...
并在泄漏列表中进行标记为Library Leak
5、修复内存泄漏
内存泄漏是由于程序错误导致的APP对不再使用的对象进行引用
可以通过下面4个步骤来修复内存泄漏问题:
- Find the leak trace. 找到泄漏痕迹
- Narrow down the suspect references. 缩小可疑引用
- Find the reference causing the leak. 找到导致泄漏的引用
- Fix the leak. 修复泄漏问题
5.1 找到泄漏问题
leak trace 是最强引用路径的短名,即从垃圾回收器根路径到内存中持有的对象的路径引用链
- 例:存储helper实例静态引用
class Helper {
}class Utils {public static Helper helper = new Helper();
}
- 通过LeakCanary 进行检测
AppWatcher.objectWatcher.watch(Utils.helper)
- 泄漏trace
┬───
│ GC Root: Local variable in native code
│
├─ dalvik.system.PathClassLoader instance
│ ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ ↓ Object[].[43]
├─ com.example.Utils class
│ ↓ static Utils.helper
╰→ java.example.Helper
通过日志我们知道是Utils中泄漏了helper实例
5.2 缩小可疑引用范围
- 例:
class ExampleApplication : Application() {val leakedViews = mutableListOf<View>()
}class MainActivity : Activity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.main_activity)val textView = findViewById<View>(R.id.helper_text)val app = application as ExampleApplication// This creates a leak, What a Terrible Failure!app.leakedViews.add(textView)}
}
- leak trace
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│ ↓ ExampleApplication.leakedViews
├─ java.util.ArrayList instance
│ ↓ ArrayList.elementData
├─ java.lang.Object[] array
│ ↓ Object[].[0]
├─ android.widget.TextView instance
│ ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.ArrayList instance
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ ↓ Object[].[0]
│ ~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance
通过分析leak trace,显示是MainActivity 里面的textView的mContext一直持有引用,导致MainActivity销毁后无法释放
5.3 找到泄漏的引用
通过前面的分析我们知道了ArrayList.elementData和Object[].[0]是实现细节,实现中ArrayList不太可能存在错误ArrayList,因此导致泄漏的引用是唯一剩余的引用:ExampleApplication.leakedViews
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│ Leaking: NO (Application is a singleton)
│ ↓ ExampleApplication.leakedViews
│ ~~~~~~~~~~~
├─ java.util.ArrayList instance
│ ↓ ArrayList.elementData
│ ~~~~~~~~~~~
├─ java.lang.Object[] array
│ ↓ Object[].[0]
│ ~~~
├─ android.widget.TextView instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance
5.4 修复泄漏问题
通过分析我们找到了泄漏点,解决方法也很简单,直接将application中leakedViews在的destory()方法中置空就行了
参考:https://square.github.io/leakcanary/
LeakCanary 使用及原理分析相关推荐
- Android LeakCanary原理分析
概述 在上一篇LeakCanary使用详细教程中,我们熟悉了LeakCanary的使用和初步描述了它的工作机制,这篇我准备从源码的角度去分析LeakCanary的工作原理: 源码分析 从上一篇中我们知 ...
- java signature 性能_Java常见bean mapper的性能及原理分析
背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...
- Select函数实现原理分析
转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...
- spring ioc原理分析
spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...
- 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...
- 原理分析_变色近视眼镜原理分析
随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...
- jieba分词_从语言模型原理分析如何jieba更细粒度的分词
jieba分词是作中文分词常用的一种工具,之前也记录过源码及原理学习.但有的时候发现分词的结果并不是自己最想要的.比如分词"重庆邮电大学",使用精确模式+HMM分词结果是[&quo ...
- EJB调用原理分析 (飞茂EJB)
EJB调用原理分析 EJB调用原理分析 作者:robbin (MSN:robbin_fan AT hotmail DOT com) 版权声明:本文严禁转载,如有转载请求,请和作者联系 一个远程对象至少 ...
- 深入掌握Java技术 EJB调用原理分析
深入掌握Java技术 EJB调用原理分析 一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在 ...
最新文章
- 信阳农林技术学院经纬度_信阳农林学院全景-360度,720度,高清全景地图-expoon网展...
- Reference和ReferenceQueue
- ubuntu下svn命令
- linux 递归修改所有权限
- php 替换某个字符,php中如何替换字符串中的某个字符-PHP问题
- python __getitem__()方法==>可以直接通过P[key]做运算
- cocosstdio之字体之文本和FNT字体
- keepalived mysql双主架构图_MySQL数据库架构和同步复制流程
- 艾伟:微软是在向谁“献刀”
- UVa 455 - Periodic Strings
- 单模光纤和多模光纤的区别,及光纤收发机(光电收发器)的介绍
- 基于SSM开发的房屋租赁系统 JAVA
- 世界著名汽车标志(大全)
- Java中实体类名称后缀VO,DTO的含义
- Going Deeper with Contextual CNN for Hyperspectral Image Classification
- C语言实现模拟银行存取款管理系统课程设计(纯C语言版)
- 图像处理(八)证件照蓝底换成红底,白底
- Java Vector API的使用测试
- C语言课设飞机票订购系统
- SQL基础编程——介绍及基本语法了解
热门文章
- 专接本计算机专业课难吗,河北计算机专业专接本难吗
- 20154313 刘文亨 EXP6
- 使用nginx问题记录(1):2021-03-29 nginx: [emerg] unknown directive “锘? in C:\nginx-1.16.1/conf/nginx.conf:3
- 景联文科技:语音工程系列(一)——语音标注的应用场景
- Java中的互斥锁介绍
- Chrome-使用代理-proxy-SwitchySharp的安装与设置
- 希捷原装移动硬盘拆壳
- UML之3——图(简介)
- selenium获取京东前三页奶瓶信息
- 免费的线框工具,UI设计工具,PDFs,资源等