做者:杨超,腾讯移动客户端开发 工程师
商业转载请联系腾讯WeTest得到受权,非商业转载请注明出处。
原文连接:http://wetest.qq.com/lab/view/359.htmlhtml
历时五天的内存优化已经结束,这里总结一下这几天都作了什么,有哪些收获。优化了,或能够优化的地方都有哪些。(由于不少事还没作,有些结论须要必定样本量才能判定,因此叫一期)一期优化减小JavaHeap内存占用约26.5M。java
在任何性能优化以前,要作的第一件事就是找到性能瓶颈!而找到性能瓶颈一般须要强大的debug工具辅助。内存方面Android有 AndroidStudio 的 Android Profiler、Allocation Tracker,以及Eclipse的MAT用于分析java的内存占用,至关强大。而偏向native层面的内存占用则找不到太好的工具,所以这里在作优化前,先造了几个工具。python
1. 线程建立分析工具android
该工具使用native hook的方式,直接hook了pthread_create调用,并记录每个线程建立时的堆栈,并打印日志。同时维护一个running thread的集合,必要时 dump下来全部running thread的建立堆栈,用于分析野蛮线程建立的场景。以及对应的日志分析工具。git
二、 Linux /proc/<pid>/smaps 文件分析脚本github
主要用于跟踪进程的 Code 部份内存(见下文)占用,分析出占用内存较多的dex,so文件。排查第三方SDK占用过多内存场景。网上只能找到一个perl脚本,功能不是很强大,鉴于笔者不熟悉perl的语法规范,改起来会比较困难,所以直接用python重写了一个。数据库
代码在这里:https://gist.github.com/LanderlYoung/aedd0e1fe09214545a7f20c40c01776c数组
三、 快速Dump Android java heap脚本缓存
由于分析内存须要不少dump操做,因此干脆写了个Bash脚本。性能优化
Bash脚本连接:https://gist.github.com/LanderlYoung/9cd0f49e49e42746622cc8e7b4bbcc8a
(顺便提一下,android提供的 hprof-conv 工具备个参数 -z 用于排除zygote的内存,十分便利。)
一般咱们在系统的内存管理页面看到的内存占用是进程的PSS,也就是整个进程的内存占用,所以咱们作优化的要考虑到全部的内存,不只仅是Java Heap。
使用Android Studio(3.0 beta)的 Android Profiler工具。
咱们能够很清晰的看到
1)进程总内存占用: 180M
2)JavaHeap: 48M
3)NativeHeap:native层的 so 中调用malloc或new建立的内存 —— 28M
4)Graphics:OpenGL和SurfaceFlinger相关内存 ——58M
5)Stack:线程栈——1.89M
6)Code:dex+so相关代码占用内存——37.75M
7)Other:蜜汁存在
上述6中内存占用除了两种不须要考虑,其余5中统统须要优化。不须要考虑的是:
1)Other:暂时无从分析
2)Graphics:若应用没有直接调用OpenGL,则能够肯定这部份内存是由Android Framework操控的,能够忽略。(固然对于游戏类应用,这里确定是优化重点。)
下面按照内存分类分开逐一介绍分析方法,和结论:
JavaHeap
这里必然是内存优化的重点,无需多言。可是企鹅FM的业务,UI,代码已经比较庞大,分析起来会显得力不从心。所以这里主要从两个方面入手,但愿能总结出一套分析方法。
一、分析应用 静息态 内存占用。
所谓静息态,是笔者自行定义的概念:
应用在退后台以后,不保留活动的场景下的内存占用。
为何要考察这个维度?由于这个是一个应用内存占用最低点的时候,后续打开任何Activity内存只会更多,不会更少!
二、分析方法
1)开发者选项开启“不保留活动”
2)进入MainActivity,滑动页面,操做一下
3)退后台,Android Studio中强制执行GC
4)dump java heap (注意上面提到的 hprof-conf 加上 -z 参数排除zygote的干扰)
5)MAT 分析 dump 下来的JavaHeap
重点介绍一下MAT:
这里能够直接打开domanitor_tree看占用内存最多的实例。
从这里按照RetainedHeap倒序排列,一点一点的排查内存占用。很容易发现不正常的内存状况。
在企鹅FM中发现:
1)图片的内存级缓存退后台没清空(此处属于onTrimMemory回调的处理有误),占用10M内存
2)ImageMisc — 280k
①
② 是一个buffer,能够在不用的时候释放内存
③ 优化目标,完全干掉
3)播放页应用动画的关系,UI是单例。其中相关View占用数百K内存,而button的icon直接引用住了5-6M的bitmap资源。
4)播放列表存储了103个ShowInfo,每一个ShowInfo 22k,总计内存约2.24M,ShowInfo冗余信息不少,能够考虑优化数据结构
5) DanmuManager — 510k
● mDanmuItemManager 内含众多弹幕
● 每条弹幕6k
● UI相关数据,离开播放页后应该清理弹幕(由于无需展现了)
● 优化目标,完全干掉。
6) FileCacheService — 362k
①
② 其中缓存了每个cache entry,其中图片缓存较多
③ 每个entry记录完整文件路径其比较长,所以路径的字符串占用了不少内存
④ 优化方案:
● 文件Parent能够共用同一个File对象。
● entry = new File(parent, “entry_name”)
⑤ 优化目标 到100k
7)LiveRoomShowListManager -- 287k
①
② 优化目标:UI相关数据,离开界面应该完全干掉
8) DB InsertHelper, Sql Statement clearBinding
① 700K到2M
② InsetHelper中会引用住最后一次执行DB insert调用的 数据(占位符)
③ InsertHelper的占位数据能够在insert完成以后清掉
针对上面提到的ShowInfo的数据结构优化
拟定优化方案:
1)ShowList存储的ShowInfo数量过多,30个足矣。
2)ShowInfo中Album字段占用10k内存,其实同一个ShowList中大多数album是彻底一致的(好比专辑类型的ShowList,主播类型的,自选集类型的,本地专辑的,etc...)。
预计内存占用 2M -> 30*12K = 360K
3)静息态内存优化总结:
上述几点加起来预期能够减小内存占用:
10M + 280K + 5M + 2M + 510K + 260k + 287k + 1M = 约20M
三、 MainActivity 操做一段时间以后内存增量
上面分析的是静息态内存,下面看一下MainActivity操做一点时间以后,内存有怎样的变化。
这里采用的方式是:
1)dump静息态内存
2)进入MainActivity,当即dump内存
3)操做一段时间以后再dump内存
一共有三次dump,能够利用MAT对比heap的功能对比内存增量。
打开MAT的historgram视图
工具栏最右边有个双箭头的icon,点击可对比dump:以下图
增量最多的仍是Bitmap(底层用byte[]存储),借助MAT的 Finer 工具能够直接看到Bitmap的图片。
这里发现的几个问题是(时间关系,应该屡次测试的,会发现更多问题):
① Banner的大图没有 Clip 致使 分辨率 很高
② 分类页的 配置区域 没有Clip
③ onRecycle没有清除掉已经引用的Bitmap,致使引用住不能gc
主要说一下第3点,是Banner每个Item有一个大图作背景,当item的view被回收的时候,相应的ImageView仍然持有着大图,致使其不能回收。这里发现了4张1M+的大图,其实理论上应该只有1张。
这个问题能够推广到全部的ListView场景,建议方式是:
替换为RecyclerView,在view回收的时候,ScarpView释放图片引用。
此外,MainActivity有5个tab,各个tab之间其实会用到相同的View(listview 的item),若是使用RecyclerView能够作到5个tab的RecyclerView共同复用同一个RecyclerPool,在节省内存的同时还能显著提升性能。
这里不方便直接测试内存占用,预估能够节省内存5-10M。
四、 正常操做应用,观察内存占用图表是否有突起
这里主要用来测试异常内存分配的场景。
这里仍然须要很大人力,过不少页面。
目前发现问题有:
1)service进程,发送wns请求的时候,内存异常增加2-3M。
这时可使用AllocationTracker工具(点击下图工具栏的红点),记录峰值那一段内存的分配,如图:
这里能够直接看到分配的栈,定位过去看,发现是这样的代码,由于head是一个65536长的数组(在 com.tencent.wns.session.Session 的构造函数写死的长度),这里建立string就浪费了超大量的内存。建议能够改为下图弹窗里的样子
2)另一个问题是播放进程,在切换节目的时候内存会忽然增加2-3M,简单跟进去看是exo建立buffer。彷佛有问题,须要再多分析一下~
Native Heap
目前能看到的NativeHeap大小是
应用启动:26M 此时已经初始化了 X5内核和IM SDK
UGC录音:26M->34M 退出以后时32M,还有部分没释放,疑似内存泄漏
发起直播:32M->72M 退出以后42M,一样没有彻底释放
具体内部占用状况还没测。。。(都说了是一期)。
官方文档:
https://source.android.com/devices/tech/debug/native-memory
Code
这一段明显看到占用了不少内存。各个场景下的使用状况是:
1)刚进入应用:38M
2)再使用UGC录音:38.28M
3)再使用视频直播(发起直播):46M
4)打开应用内WebView(X5内核):56M
以上是主进程的内存,占用至关多。须要注意的是code内存占用通常是经过read-only方式mmap映射到内存中的的dex、odex、so等文件,所以在内存紧张的状况下,系统会回收这些内存,只是在oom-killer中仍然会计算在内。
另外播放进程2.27M,service进程1.1M还属于比较正常的水平。
显然主进程的Code内存占用太多了,须要分析。这里经过解析Linux标准的 /proc/<pid>/smaps文件,这个文件记录了进程内每一段虚拟内存的文件映射状况,这个文件只有进程本身有读权限,因此要么用root的机器,要么就本身写段代码copy出来。结合上面提到的工具。分析结果以下:
● 应用so占用 app so map Rss = 3984 kB (其中IM SDK 2576k)
● 应用的dex占用 app dex map Rss = 15101 kB
● X5内核的so+dex内存占用 tbs mem map Rss = 29048 kB
● 直播so相关 avlive mem map Rss = 3092 kB
● 其中X5内核的代码没有打进apk,所以能够比较独立的统计出来,占用有29M之多,让人惊讶!
● 其次直播的java代码打进了apk不方便单独统计内存用量,可是so是独立加载的,内存占用3M也是很多的。
● 最后是应用自身的dex占用有15M之多,由于自身代码量很大,彷佛能够理解,可是仍然不少啊!
这里须要考虑的是 X5 内核可否延时加载?由于没打开WebView的时候就已经占用了数M了。另外WebView关闭以后是否能够销毁。
直播相关SO,能够考虑直播退出以后从内存中卸载掉。(java规范是加载so的classloader被GC,相关so便可卸载)。
应用自身dex占用。android 8.0 对art优化一个叫作DexLayout 的能力,应为mmap映射的文件不会被当即加载进内存,在用到的时候是按照页大小(4k)加载的,当用到的类在dex中分布很分散的时候,就会致使盲目加载不少页,DexLayout就是把热点类集中放到一块儿。这里FaceBook推出了ReDex工具,能够参考一下。
PS:关于DexLayout
在AndroidStudio的Memory Profiler中没有线程数这个维度。可是运行中,主进程的线程数量一般会在100个左右,这是个惊人的数字,要知道Mac版的AndroidStudio也不过77个线程。。。。请自行体会一下。
关于线程的建立和内存占用,请参考笔者的另外一篇文章:《Android 建立线程源码与OOM分析》 。
这里分析用的自制工具,dump下载全部running的线程,和他们建立时的堆栈。
结果是:
● X5:25个线程(简直。。。)
● IMSDK:17个线程
● StackBlur:8个线程
● WNS:7个线程
● ImageLoader:6个线程
● magnifiersdk:5个线程
须要注意,这里的栈和线程名,是建立线程的时候的调用栈,以及对应的线程名(而不是子线程名)
事实上,用一样的方法,还能够分析一下进程历史中全部建立过的线程,统计哪里建立线程最多。
一般来讲,全部线程应该有应用统一的线程池来管理,sdk内部须要线程池,应该有外部注入一个线程池来提供给sdk使用。
若是有其余状况,如:不是在线程池建立的线程,在sdk本身的线程池里建立的线程,这种均可能致使线程数量的野蛮增加,须要联系sdk的开发人员杜绝这种状况。
以上就是这5天的工做结果:
java内存占用基本合理,静息态 内存占用能够优化20M,MainActivity运行时的内存占用能够优化5-10M。
code内存占用太多,其中X5内核占用29M实在太多,须要考虑优化。
应用内的线程数量主要有X5内核,IMSDK和WNS贡献,外网线程建立的OOM crash 系WNS的bug,须要联系相关sdk开发人员。
最后是Native内存占用尚未详细分析,暂时看不到使用状况。可是能够知道目前的结论是:Native内存占用不少,且应该存在内存泄漏。
PS: 实际效果反馈
按照上述分析结果,进行了相关的代码调整。
执行的点包括:
一、IntelliShowList pageSize 50->20
二、IntelliShowList 公用Album结构
三、Afc-db clearBinding after insert, 数据库
四、Afc-FileCacheService cache Entry with fileName not full path, 文件缓存
五、 fix onTrimMemory bug,退后台清空图片内存缓存
六、播放页相关控件,退后台以后清掉icon,释放bitma引用
未执行的点包括:
一、播放页的bottomPannel部分icon由于逻辑较为复杂,暂时未进行处理。预计内存占用1M
二、PlayLogic的historyList逻辑复杂暂时未处理,预计内存占用500K
三、24h直播间LiveRoomShowListManager -- 287k
四、DanmuManager — 510k
五、通过ice提醒,下载节目的record也会所有加载进内存。每一个ShowInfo 22k,内存占用取决于用户下载的节目数。
效果对比:
before:39.32M
after:12.88M
优化内存占用 26.44M!
UPA—— 一款针对Unity游戏/产品的深度性能分析工具,由腾讯WeTest和unity官方共同研发打造,能够帮助游戏开发者快速定位性能问题。旨在为游戏开发者提供更完善的手游性能解决方案,同时与开发环节造成闭环,保障游戏品质。
目前,限时内测正在开放中,点击http://wetest.qq.com/cube/ 便可预定。
对UPA感兴趣的开发者,欢迎加入QQ群:633065352
若是对使用当中有任何疑问,欢迎联系腾讯WeTest企业QQ:800024531