内存泄露分析定位
- 内存泄露根本原因
- 内存泄露常见情况
- 内存泄露定位方案
- 最近碰到的示例
- 常见内存泄露解决方案
该章节需配合
Java GC机制 一起看
0x0001. 内存泄露根本原因
-
GCRoot对象长期持有其他已经无用的对象(本该被回收,却未被回收)。通俗的说就是 生命周期较长的对象(如 static 类)持有生命周期较短的对象的引用(某个 Activity) -
GCRoot对象包括-
方法区静态属性 ,常量(多数伴随整个虚拟机的生命周期)
-
栈帧中本地变量表中的引用对象 (即当前线程正在运行的代码持有的对象)
-
JNI 本地方法引用的对象 (native 区引用的对象)
-
0x0002. 内存泄露常见情况
-
静态对象
-
static activity -
static view
-
-
activity/fragment销毁,结束耗时任务(异步网络请求) -
inner class存在异步等情况 -
SingleInstance持有局部生命周期Activity -
资源类
cursor没有关闭
补充,
lamda表达式是匿名内部类
0x0003. 内存泄露定位方案
-
静态分析
跑
Lint,注意看Warning,会针对handler等,进行提示 -
动态分析
leakCanaryadb shell dumpsys meminfo [PackageName]Android Profiler(见下方内存泄露实例详解)
以上三者,根据个人经验可以结合起来使用
leakCanary可以用于快速定位内存泄露问题- 当面对
leakCanary无法直接确定内存泄露的时,可以用adb shell dumpsys进行辅助确认泄露activity - 根据前两者确定的
activity配合Android ProfilerGC后进行分析,查看没有回收的对象的引用链,即可定位内存泄漏位置
0x0004. 最近碰到的示例
背景介绍: 一个商品详情页,有内存泄露 。
-
LeakCanary报错截图,发现是一个UI消息队列上的一条消息泄露了,无法直接定位具体代码。

-
此时,利用
adb shell dumpsys meminfo,然后多次进出SpuDetailActivity,验证确认是SpuDetailActivity存在泄露。
➜ adb shell dumpsys meminfo com.leixun.taofen8.haoda
Applications Memory Usage (in Kilobytes):
Uptime: 693212294 Realtime: 787382396
** MEMINFO in pid 14754 [com.leixun.taofen8.haoda] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 74230 74108 0 230 98688 86999 11688
Dalvik Heap 6442 5952 0 25 11519 5760 5759
Dalvik Other 2683 2680 0 4
Stack 84 84 0 0
Ashmem 2 0 0 0
Gfx dev 28316 28316 0 0
Other dev 28 0 28 0
.so mmap 15415 280 7720 7
.jar mmap 3762 0 1844 0
.apk mmap 432 0 132 0
.ttf mmap 107 0 48 0
.dex mmap 19419 19344 32 0
.oat mmap 621 0 4 0
.art mmap 4696 4108 0 9
Other mmap 5421 476 3348 0
Unknown 2842 2808 0 6
TOTAL 164781 138156 13156 281 110207 92759 17447
App Summary
Pss(KB)
------
Java Heap: 10060
Native Heap: 74108
Code: 29404
Stack: 84
Graphics: 28316
Private Other: 9340
System: 13469
TOTAL: 164781 TOTAL SWAP PSS: 281
Objects
Views: 341 ViewRootImpl: 2
AppContexts: 7 Activities: 2
Assets: 18 AssetManagers: 0
Local Binders: 25 Proxy Binders: 37
Parcel memory: 11 Parcel count: 48
Death Recipients: 2 OpenSSL Sockets: 13
WebViews: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
Asset Allocations
: 162K
-
由于个人经验丰富,直接借助
LeakCanary和adb shell dumpsys meminfo配合几次试错,定位到问题整个推导流程:
UI->Handler->MutableLiveData->MutableLiveData.observe()UI消息发送,自己代码中没有handler之类的,用了只有MutableLiveData来更新View,快速定位两个LiveData Observe的代码,配合adb shell dumpsys meminfo直接验证注释掉几个方法,看Activity数量,快速定位出问题出在spuDetailBinding.hdPtrList.refreshComplete(), 该第三方库调用错误时会出现内存泄露。... viewModel.loadSuccess.observe(this, aBoolean -> { if (aBoolean) { adapter.submitList(viewModel.itemViewModels); } hideProgressDialog(); spuDetailBinding.hdPtrList.refreshComplete(); }); viewModel.loadRelatedSuccess.observe(this, aBoolean -> { adapter.submitList(viewModel.itemViewModels); }); ... -
Android Profiler内存进行举例性分析,定位问题所在
使用
profiler的时候,最好把leakCanary去掉,影响手动GC,而且还会引起卡顿不熟练
profiler,可以配合 Android Profiler 官方使用指南食用
-
连接
profiler

-
手动触发
GC

-
选中内存区域,找到泄露的
Activity

-
根据之前
LeakCanary报错,直接定位MessageQueue持有的对象

0x0005. 常见内存泄露解决方案
WeakReference进行包裹- 用谷歌官方提供的生命周期组件,类似
MutableLiveData之类的