1、问题背景java
测试使用monkey工具,压力测试主题商店应用时,最后出现电话本内存泄露信息:linux
appCrashed_android.process.contacts_2016-12-30 00_34_36.txt
// CRASH: android.process.contacts (pid 5172)android
// Short Msg: java.lang.OutOfMemoryErrorgit
刚开始看到这个问题,感受一头雾水,为什么测试主题商店应用,会致使电话本内存泄露呢? 这两个看起来八竿子打不着的应用, 会存在着怎样的纠葛,当时不知。github
项目要继续,问题要解决。以前没有处理过OOM问题,只好查阅资料,询问同事协助处理。shell
2、问题分析app
首先想确认问题的复现几率, 测试说基本上跑个两三个小时的monkey测试,就能复现,因而我本身也本地在尝试,果真可否复现。向导主题商店的测试内容主要为切换主题,因而就编写脚本,工具
压力测试切换主题,看可否使问题复现的时间缩短。测试
切换主题的脚本:spa
@echo off
adb remount
adb push 240699_Men.theme /sdcard/Themes/
pause
echo --------start to change themes-----
##循环切换主题, 1000次
FOR /l %%i IN (1,1,1000) DO (
adb shell am startservice -n com.nearme.themespace/com.nearme.themespace.services.ThemeApplyService --es THEME_PATH /sdcard/Themes/240699_Men.theme
##每次间隔8秒钟
ping -n 8 127.0.0.1>nul
echo %%i
)
pause
经过脚本运行,果真复现时间缩短了不少,很快就能复现。
接下来就是查找哪里出现的问题了,在网上找到一篇老外分析短信内存泄露的方法,分析中有如何使用脚本工具获取内存信息,很好用:
http://stevevallay.github.io/blog/2014/11/17/memory-leak/
文中说明为了获取内存信息,咱们须要用到linux提供的
procrank
procmem
这两个工具,通常咱们的手机中不会自带这两个工具,须要去编译的版本中查找拷贝过来使用。
具体项目工具适配方法:
一、因为procmem和 Procrank 工具在咱们的手机中通常没有放置,所以在调试各个项目时须要到各个项目编译的out下面的去找到对应的工具以及so库拷贝到目录中进行替换使用,
目录根据机器是32位仍是 64位 有所不一样:
out\target\product\msm8952_64\system\lib
out\target\product\msm8952_64\system\lib64
二、bat脚本也须要根据32位和64位push so库到对应目录, shell脚本须要根据须要调试的应用进程进行修改:
放置工具到手机中的bat脚本以下:
adb remount
adb shell rm -rf /sdcard/mem_info_log/
adb push libpagemap.so /system/lib64
adb push procrank /system/xbin
adb push procmem /system/xbin
adb shell chmod 777 /system/xbin/procrank
adb shell chmod 777 /system/xbin/procmem
pause
###放置到手机后台运行导出内存信息的shell脚本
adb push log.sh /system/bin
adb shell chmod 777 /system/bin/log.sh
adb shell sh /system/bin/log.sh &
pause
在手机中运行的shell脚本文件参照老外的写法以下:
#!/system/bin/sh
set `ps| grep android.process.contacts`
pidOfMms=$2
echo "pid of contacts is: $pidOfMms"
set `ps|grep com.nearme.themespace`
pidOfPhone=$2
echo "pid of themespace is: $pidOfPhone"
if [ ! -d "/sdcard/mem_info_log" ]; then
mkdir "/sdcard/mem_info_log"
echo "create new dir /sdcard/mem_info_log"
fi
while [ -d "/sdcard/mem_info_log" ]
do
echo "start capture mem_info_log"
t=`date +"%Y-%m-%d-%H-%M-%S"`
echo $$ > /sdcard/mem_info_log/pid.log
procrank > "/sdcard/mem_info_log/procrank"$t".log"
procmem -p $pidOfMms > "/sdcard/mem_info_log/procmem.contacts"$t".log"
procmem -p $pidOfPhone > "/sdcard/mem_info_log/procmem.themespace"$t".log"
dumpsys meminfo android.process.contacts > "/sdcard/mem_info_log/meminfo.contacts"$t".log"
dumpsys meminfo com.nearme.themespace > "/sdcard/mem_info_log/meminfo.themespace"$t".log"
cat /proc/$pidOfPhone/maps > "/sdcard/mem_info_log/maps.themespace"$t".log"
cat /proc/$pidOfMms/maps > "/sdcard/mem_info_log/maps.contacts"$t".log"
sleep 300
done
脚本中每隔五分钟会使用
procrank
procmem
工具分别导出手机在压力测试过程当中当前的内存信息,造成一个个文本放置在手机的一个目录下面,便于后续分析。
其中最终看到的有用信息在memInfo中:
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 4160 3992 0 0 7936 6563 1372
Dalvik Heap 13589 13464 0 0 23033 22945 88
Dalvik Other 635 632 0 0
Stack 212 212 0 0
Ashmem 6 4 0 0
Other dev 4 0 4 0
.so mmap 797 80 28 0
.apk mmap 479 0 116 0
.ttf mmap 11 0 0 0
.dex mmap 3644 8 3632 0
.oat mmap 2844 0 796 0
.art mmap 1716 940 0 0
Other mmap 96 8 16 0
Unknown 110 104 0 0
TOTAL 28303 19444 4592 0 30969 29508 1460
App Summary
Pss(KB)
------
Java Heap: 14404
Native Heap: 3992
Code: 4660
Stack: 212
Graphics: 0
Private Other: 768
System: 4267
TOTAL: 28303 TOTAL SWAP (KB): 0
Objects
Views: 389 ViewRootImpl: 1
AppContexts: 7 Activities: 4
Assets: 3 AssetManagers: 2
Local Binders: 62 Proxy Binders: 27
Parcel memory: 9 Parcel count: 36
Death Recipients: 0 OpenSSL Sockets: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
看到了Activities的应用在持续增加,这样就可以确认应用确实存在OOM了,因而开始查对应Activity里面的代码。
最后发如今一个activity中存在了注册观察者处理,可是没有注销,其注册方式为:
getActivity().getContentResolver().registerContentObserver
问题的巧合之处就是本来正常的activity的生命周期其实每次就一次onCreate, 注册的调用是在这个方法中,若是是反复直接进入这个activity, 最多也就有一次引用,
由于再次进入是直接运行onstart的生命周期,不会在走入onCreate中了,因此若是反复测试电话本应用自己,此问题复现不了,反而是因为切换主题后,致使电话本又从新运行了
onCreate方法,这样每次都会运行注册,增长引用次数,致使后面出现引用过多,activity迟迟不能回收后致使OOM, 使用脚本观察,发现通常在15次左右就会出现OOM, 此时
电话本应用的内存已经接近120M, 这应该也是系统分配给一个应用的最大内存使用数值。
3、问题解决
固然发现问题缘由后,解决起来就比较容易了,在onDestroy中增长注销便可:
getActivity().getContentResolver().unregisterContentObserver
随后再网上查阅了一下经常使用的OOM问题:
https://juejin.im/entry/5762b1d7816dfa00544680a0
我这个问题就是属于
因此注册和注销必定要记得成对使用。
问题事后又细想了一下为什么这种注册没有注销会致使OOM, 因而猜想这种监听器的底层实现应该是使用了静态static 列表,将每一个注册传递过来的activity或者其余参数
放入静态列表中,因此会致使没法回收。 并非全部的用getActivity()做为参数传递过去以后都会致使OOM,好比初始化中获取资源文件,有时候也会使用这个方法传递参数
做为context, 这种就是没有问题的:
FeatureOption.init(getActivity());
不要风声鹤唳,之后全部的地方都不敢获取activity 传递参数了。