[TOC]
## Memory Profiler
`Memory Profiler`是`Profiler` 中的其中一个版块,`Profiler` 是 `Android Studio` 为我们提供的性能分析工具,使用 `Profiler` 能分析应用的 `CPU`、内存、网络以及电量的使用情况。
使用`Memory`可以检测以下功能
1.查看内存曲线及内存占用情况
2.可以定位是否存在内存抖动问题
3.堆转储(`Dump Java Heap`)可检测出内存泄漏的对象
![](https://img.kancloud.cn/c8/65/c8652b7c610b836fa9895571f3281e55_1240x761.png)
关于`Memory Profiler`的具体使用就不在此缀述了,想要了解的可参考:[什么是 Memory Profiler?](https://juejin.cn/post/6844903897958449166#heading-48 "https://juejin.cn/post/6844903897958449166#heading-48")
* **Java**:从 Java 或 Kotlin 代码分配的对象内存。
* **Native**:从 C 或 C++ 代码分配的对象内存。
即使您的应用中不使用 C++,您也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表您处理各种任务,如处理图像资源和其他图形时,即使您编写的代码采用 Java 或 Kotlin 语言。
* **Graphics**:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。 (请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
* **Stack**: 您的应用中的原生堆栈和 Java 堆栈使用的内存。 这通常与您的应用运行多少线程有关。
* **Code**:您的应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。
* **Other**:您的应用使用的系统不确定如何分类的内存。
## showmap
这对于我们来说非常有用,可以确定进程中哪些库占用内存比较多
~~~
dumpsys meminfo com.sangfor.pocket
ps | grep com.sangfor.pocket
su
showmap [pid]
~~~
## Memory Analyzer Tool
`MAT`工具可以帮助开发者定位导致内存泄漏的对象,以及发现大的内存对象,然后解决内存泄漏并通过优化内存对象,以达到减少内存消耗的目的。
比起`Memory Profiler`,`MAT`使用起来更加麻烦,同时现在`Memory Profiler`功能也越来越强大了,所以我现在已经很少使用`MAT`了
如果想要更多地了解`MAT`,也可以参考:[什么是Memory Analyzer Tool](https://juejin.cn/post/6844903897958449166#heading-52 "https://juejin.cn/post/6844903897958449166#heading-52")
## LeakCanary
eakCanary是一个内存泄漏检测的框架,默认只会检测Activity的泄漏,如果需要检测其他类,可以使用LeakCanary.install返回的RefWatcher,调用RefWatcher.watch(obj)就可以观测obj对象是否出现泄漏。
### 基本原理
LeakCanary 采用的是 Reference + ReferenceQueue来检测一个对象是否被回收,大概需要几个步骤:
* 创建一个引用队列 queue
* 创建 Refrence 对象,并关联引用队列 queue
* 在 reference 被回收的时候,refrence 会被添加到 queue 中
### 基本流程
![](https://img.kancloud.cn/1d/37/1d3759080e64e45409577643ce1505d5_862x675.png)
#### 检测泄露
观察对象:`Activity`,`Fragment`,`RootView`,`Service
1. 传入的观察对象都会被存储在`watchedObjects`中
2. 会为每个`watchedObject`生成一个`KeyedWeakReference`弱引用对象并与一个`queue`关联,当对象被回收时,该弱引用对象将进入`queue`当中
3. 在检测过程中,我们会调用多次`removeWeaklyReachableObjects`,将已回收对象从`watchedObjects`中移除
4. 如果`watchedObjects`中没有移除对象,证明它没有被回收,那么就会调用`moveToRetained`
#### 触发堆快照,生成`hprof`文件
1. 如果`retainedObjectCount`数量大于0,则进行一次`GC`,避免额外的`Dump`
2. 默认情况下,如果`retainedReferenceCount<5`,不会进行`Dump`,节省资源
3. 如果两次`Dump`之间时间少于60s,也会直接返回,避免频繁`Dump`
4. 调用`heapDumper.dumpHeap()`进行真正的`Dump`操作
5. `Dump`之后,要删除已经处理过了的引用
6. 调用`HeapAnalyzerService.runAnalysis`对结果进行分析
分析`hprof`文件的工作主要是在`HeapAnalyzerService`类中完成的
关于`Hprof`文件的解析细节,就需要牵扯到`Hprof`二进制文件协议,通过阅读协议文档,`hprof`的二进制文件结构大概如下:
![](https://img.kancloud.cn/7f/9e/7f9e33fbe3cad99283d8ee58bd184cdf_968x220.png)
解析流程如下所示:
![](https://img.kancloud.cn/36/08/3608ff9adc07a3071e8cb7b010863a68_193x586.png)
简要说下流程:
1.解析文件头信息,得到解析开始位置
2.根据头信息创建`Hprof`文件对象
3.构建内存索引
4.使用`hprof`对象和索引构建`Graph`对象
5.查找可能泄漏的对象与`GCRoot`间的引用链来判断是否存在泄漏(使用广度优先算法在`Graph`中查找)
### 为什么`LeakCanary`不能用于线上?
1. 每次内存泄漏以后,都会生成一个`.hprof`文件,然后解析,并将结果写入`.hprof.result`。增加手机负担,引起手机卡顿等问题。
2. 多次调用`GC`,可能会对线上性能产生影响
3. 同样的泄漏问题,会重复生成 `.hprof` 文件,重复分析并写入磁盘。
4.`. hprof`文件较大,信息回捞成问题。
了解了这些问题,我们可以尝试提出一些解决方案:
1. 可以根据手机信息来设定一个内存阈值 `M` ,当已使用内存小于 `M` 时,如果此时有内存泄漏,只将泄漏对象的信息放入内存当中保存,不生成`.hprof`文件。当已使用大于 `M` 时,生成`.hprof`文件
2. 当引用链路相同时,可根据实际情况去重。
3. 不直接回捞`.hprof`文件,可以选择回捞分析的结果
4. 可以尝试将已泄漏对象存储在数据库中,一个用户同一个泄漏只检测一次,减少对用户的影响
## ResourceCanary
### LeakCanary的缺陷
LeakCanary分析可见其对 Activity 是否泄漏的判断依赖VM会将可回收的对象加入 WeakReference 关联的 ReferenceQueue 这一特性,Matrix发现这中做法在个别系统上可能存在误报,原因大致如下:
* VM 并没有提供强制触发 GC 的 API ,通过 System.gc()或 Runtime.getRuntime().gc()只能“建议”系统进行 GC ,如果系统忽略了我们的 GC 请求,可回收的对象就不会被加入 ReferenceQueue
* 将可回收对象加入 ReferenceQueue 需要等待一段时间,LeakCanary 采用延时 100ms 的做法加以规避,但似乎并不绝对管用
* 监测逻辑是异步的,如果判断 Activity 是否可回收时某个 Activity 正好还被某个方法的局部变量持有,就会引起误判
* 若反复进入泄漏的 Activity ,LeakCanary 会重复提示该 Activity 已泄漏
### ResourceCanary的改进
对此做了以下改进:
* 增加一个一定能被回收的“哨兵”对象,用来确认系统确实进行了GC
* 直接通过WeakReference.get()来判断对象是否已被回收,避免因延迟导致误判
* 若发现某个Activity无法被回收,再重复判断3次,且要求从该Activity被记录起有2个以上的Activity被创建才认为是泄漏,以防在判断时该Activity被局部变量持有导致误判
* 对已判断为泄漏的Activity,记录其类名,避免重复提示该Activity已泄漏
### 修复系统潜在内存泄漏
Matrix ResourceCanary 还针对常见的系统内存泄漏做了针对性的修改。
[InputMethodManager 导致的泄漏](https://www.jianshu.com/p/62e498075e56)
[Drawable泄露](https://www.jianshu.com/p/914888735ce5)
[【带着问题学】关于LeakCanary2.0你应该知道的知识点](https://juejin.cn/post/6968084138125590541)
[探索 Android 内存优化方法](https://juejin.cn/post/6844903897958449166)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台