问题背景
问题描述:进入应用的视频素材剪辑页面然后退出,脚本循环执行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 Cleaner
next = 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 nothing
if (cl.next == cl)
return false;
// Update list
if (first == cl) {
if (cl.next != null)
first = cl.next;
else
first = 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 itself
cl.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循环引用导致,更新版本后解决。