鸿蒙抖音直播最严重的一个内存泄漏分析与解决

前言

在我接手鸿蒙稳定性之前,直播的预览流跟直播间都有严重的内存泄漏,首先就算是不进入直播间,只要刷到一次直播预览流,就会泄露一些内存。其次进入直播间的泄露更加的严重,直播间内滑动个几十次,必现闪退。

上一期的文章讲述了 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等资源的泄露,实际上每个直播间可能泄露的更多。

相关推荐
小小小小小星3 小时前
鸿蒙开发之分布式能力:方法论与技术探索
harmonyos
li理5 小时前
基础复用原理(@Reusable)
harmonyos
Andy_GF6 小时前
纯血鸿蒙 HarmonyOS Next 调试证书过期解决流程
前端·ios·harmonyos
whysqwhw7 小时前
鸿蒙@Builder@BuilderParam和wrapBuilder
harmonyos
whysqwhw7 小时前
鸿蒙路由带参数
harmonyos
whysqwhw7 小时前
鸿蒙的组件通信机制
harmonyos
幽蓝计划21 小时前
HarmonyOS元服务开发系列教程(三):实现音乐播放和封面旋转
华为·harmonyos
zhanshuo1 天前
HarmonyOS 开发实战:搞定应用名字与图标更换,全流程可运行示例
harmonyos
zhanshuo1 天前
HarmonyOS 开发实战:快速更改应用名字与图标的终极指南
harmonyos