前言
在我接手鸿蒙稳定性之前,直播的预览流跟直播间都有严重的内存泄漏,首先就算是不进入直播间,只要刷到一次直播预览流,就会泄露一些内存。其次进入直播间的泄露更加的严重,直播间内滑动个几十次,必现闪退。
上一期的文章讲述了 NodeController 循环依赖导致的内存泄露,解决了直播预览流持续泄露的问题 (预览流每刷到一个直播间就会泄露一些内存),感兴趣的可以点开下面的文章看看。juejin.cn/post/752946...
最近做了内存泄漏的二期优化,直播间内的泄露也大幅度缓解,刷上几百个也不会出现闪退问题,这期文章重点介绍下其中的一个优化项,鸿蒙 XComponent 导致的严重内存泄漏,平均每个直播间泄露30mb+。接下来讲一下XComponent 优化的启动背景以及优化思路。
背景
线上每天都有用户反馈直播间刷上几十个就会发生闪退,捞取日志都是内存不足被系统强杀,分析 js 内存快照未发现有特别严重的内存泄露。
后续华为厂商也找了我们好几次,每滑动一个直播间,内存水位就会上涨一大截,多刷几个直播间就会被强杀(这个问题我们也是已知的,修复了很多JS层内存泄漏,依然没有缓解)。
后来经过厂商内部分析,初步判断发生在XComponent 的内存泄露。

问题排查思路
根据厂商提供的信息,于是我将直播间能找到的XComponent都设置了可识别id(加了点特殊前缀),出了一个特殊的包发给了厂商。最终定位到是礼物的xComponent,并且一个直播间会有两个播放器,所以一个直播间至少会泄露27mb左右。这里有个小插曲,由于代码有误,将 XComponent 的id设置成了一个固定的值,由于XComponent存在内存泄漏,所以多刷几个直播间就会发生崩溃。所以再设置 XComponent id 的时候,一定要确保它是唯一的。
虽然就只有三个实例,根据上面的13926400基本上可以确定就是礼物播放器导致的。

于是分析礼物 XCompnent 的内存快照,发现在JS层,没有任何的内存泄露,故这个泄露大概率发生在Native层,
XComponent 拥有一块独立的缓存去上屏,这块缓存需要手动释放,
代码分析
根据华为官方的提示,业务方大概是误用了developer.huawei.com/consumer/cn... 这里的方法,我根据 OH_NativeWindow 关键字进行搜索,终于发现了可疑的用法。
经过分析,在礼物的代码里发现了下面的用法。从代码上看,貌似nativeWindow_的引用跟解引用都是成对的。
跟相关同学沟通后,他觉得这里进行了成对调用,不会有问题。
scss
// 对象在析构的时候会传一个 newWindow = nullptr 用来回收nativeWindow_
if (nativeWindow_ != nullptr) {
(void)OH_NativeWindow_NativeObjectUnreference(nativeWindow_);
}
nativeWindow_ = newWindow;
if (nativeWindow_ != nullptr) {
(void)OH_NativeWindow_NativeObjectReference(nativeWindow_);
本着试一试的想法,加日志后发现OH_NativeWindow_NativeObjectUnreference(nativeWindow_) 不会被调用,这下几乎可以石锤是这个地方的问题了。
深入分析之后,发现这里的业务逻辑错误也是非常的有意思,简单的捋一下如下:
1 release操作被post到一个循环方法里执行
2 唤醒等待锁,接着将running_置成了false。
3 唤醒锁后开始执行release操作,
4 而release中的OH_NativeWindow_NativeObjectUnreference 操作又被post了一下,但是running_已经被置成了false,
5 OH_NativeWindow_NativeObjectUnreference的post方法被拦截。
核心代码如下(已脱敏)
post逻辑
arduino
void obj::post(const Task &task) {
if (!running_) {
...
return;
}
{
...
tasks_.push_back(task);
}
if (std::this_thread::get_id() != threadId_) {
...
wakeUpCond_.notify_one();
}
}
循环逻辑:
arduino
while (running_) {
{
...
wakeUpCond_.wait();
...
}
std::vector<Task> tasks;
{
// ...
tasks.swap(tasks_);
}
for (const auto &task : tasks) {
task(*renderContext_);
}
...
}
释放逻辑
scss
OBj::~OBj() noexcept {
post( {
...
release()
...
});
running_ = false;
}
void release() {
...
post({
OH_NativeWindow_NativeObjectUnreference(nativeWindow_)
})
...
}
修复效果
找到问题之后,将post逻辑优化即可,最终修复效果也是非常的好,虽然还是有一些小的内存泄漏,但是内存增长已经很缓慢了,连续刷上百个直播间也不会崩溃了。
这里除了NativeWindow的缓存未释放以外,还有一些shader资源、gl等资源的泄露,实际上每个直播间可能泄露的更多。