内存泄露分析定位
- 内存泄露根本原因
- 内存泄露常见情况
- 内存泄露定位方案
- 最近碰到的示例
- 常见内存泄露解决方案
该章节需配合
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
等,进行提示 -
动态分析
leakCanary
adb shell dumpsys meminfo [PackageName]
Android Profiler
(见下方内存泄露实例详解)
以上三者,根据个人经验可以结合起来使用
leakCanary
可以用于快速定位内存泄露问题- 当面对
leakCanary
无法直接确定内存泄露的时,可以用adb shell dumpsys
进行辅助确认泄露activity
- 根据前两者确定的
activity
配合Android Profiler
GC
后进行分析,查看没有回收的对象的引用链,即可定位内存泄漏位置
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
之类的