问题背景
问题描述:进入应用的视频素材剪辑页面然后退出,脚本循环执行500次,内存增长156M
问题分析
分析增长曲线图
曲线反映了从0到500次脚本执行过程中adb shell dumpsys meminfo抓取内存的增长情况,可以看出是Native内存一直增长未释放。
Profiler工具分析
将执行100的hprof和500次的hprof文件分别导入Android Studio的Profiler中。
可以看到“0 Leaks”,以及Native Size只增长了200k左右。至此,Profiler体现不出内存增长的原因。
MAT工具分析
hprof格式转换
利用sdk\platform-tools中的hprof-conv.exe将hprof转换为MAT可识别的文件
hprof对比
用MAT打开100次和500次的hprof文件,然后进行对比两份hprof
可以看到WeakReference、ViewRootImpl.W、Cleaner和NativeAllocationRegistry.CleanerThunk的对象数量增长比较多。
分析ViewRootImpl.W
查看ViewRootImpl.W发现是一个和Window相关的Binder接口
查看ViewRootImpl.W的引用关系,
发现是被Cleaner所持有,得出ViewRootImpl.W增长是因为Cleaner在增长。
分析Cleaner
查看源码
public class Cleaner extends PhantomReference<Object>
{// Dummy reference queue, needed because the PhantomReference constructor// insists that we pass a queue. Nothing will ever be placed on this queue// since the reference handler invokes cleaners explicitly.//private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();// Doubly-linked list of live cleaners, which prevents the cleaners// themselves from being GC'd before their referents//static private Cleaner first = null;private Cleanernext = null,prev = null;private static synchronized Cleaner add(Cleaner cl) {if (first != null) {cl.next = first;first.prev = cl;}first = cl;return cl;}private static synchronized boolean remove(Cleaner cl) {// If already removed, do nothingif (cl.next == cl)return false;// Update listif (first == cl) {if (cl.next != null)first = cl.next;elsefirst = cl.prev;}if (cl.next != null)cl.next.prev = cl.prev;if (cl.prev != null)cl.prev.next = cl.next;// Indicate removal by pointing the cleaner to itselfcl.next = cl;cl.prev = cl;return true;}private final Runnable thunk;private Cleaner(Object referent, Runnable thunk) {super(referent, dummyQueue);this.thunk = thunk;}/*** Creates a new cleaner.** @param ob the referent object to be cleaned* @param thunk* The cleanup code to be run when the cleaner is invoked. The* cleanup code is run directly from the reference-handler thread,* so it should be as simple and straightforward as possible.** @return The new cleaner*/public static Cleaner create(Object ob, Runnable thunk) {if (thunk == null)return null;return add(new Cleaner(ob, thunk));}/*** Runs this cleaner, if it has not been run before.*/public void clean() {if (!remove(this))return;try {thunk.run();} catch (final Throwable x) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {if (System.err != null)new Error("Cleaner terminated abnormally", x).printStackTrace();System.exit(1);return null;}});}}}
查看引用关系
可以看到Cleaner类是链表结构,不再好继续往下分析了。
分析WeakReference
从上面ViewRoomImpl.W源码可以看到有WeakReference类型的成员变量,推断WeakReference增长832次有400次是因为ViewRootImpl.W增长400次。用MAT合并虚引用、软引用和弱引用
剩余没有被过滤的133个对象,查看存放的值有很多和音视频播放相关
反馈给火山SDK
屏蔽视频播放功能
视频素材剪辑页面主要有视频抽帧和视频播放功能两个功能,结合上面WeakReference的分析,怀疑视频播放功能产生了Native的泄露。删除播放器的初始化,跑脚本复测后发现内存平稳。
问题解决
火山同事发现是so中shared_ptr循环引用导致,更新版本后解决。