1 关键字

setInterval;memory leak;NativeValue;HandleScope;

 

2 问题描述

问题现象:3.2beta2分支上,应用使用定时器setInterval,应用内存持续新增

运行环境:硬件 dayu200,软件:3.2beta2

测试步骤:

  1. 使用Deveco开发定时器demo工程。

    定时器demo:

    @Entry
    @Component
    struct Index {
      build() {
        Row() {
          Column() {
            Button("开启定时器")
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .height("10%")
              .onClick(() => {
                console.log("start interval")
                setInterval(() => {
                }, 10)
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
  2. 安装应用,点击按钮,触发定时器。

  3. 通过Deveco的Profiler监控应用内存。

 

3 问题原因

3.1 正常机制

  • 开启定时器后,应用内存无明显新增。

 

3.2 异常机制

  • 开启定时器后,应用内存持续新增。

 

4 解决方案

  • 将engine.CallFunction(engine.CreateUndefined(), jsFunction->Get(), args.data(), args_.size())中的参数engine.CreateUndefined()拿出来申明,修改为:

    // engine.CallFunction(engine.CreateUndefined(), jsFunction_->Get(), args_.data(), args_.size());
    NativeValue* nativeValue = engine.CreateUndefined();
    engine.CallFunction(nativeValue, jsFunction_->Get(), args_.data(), args_.size());

     

5 定位过程

该问题为运行时异常:

  • 根据问题现状进行初步分析:调用定时器应用内存持续新增,那么应该是有对象频繁创建,且不被回收

  • 查看foundation\ability\ability_runtime\frameworks\native\runtime\js_timer.cpp源码:

    class JsTimer final {
    public:
        JsTimer(JsRuntime& jsRuntime, const std::shared_ptr<NativeReference>& jsFunction, const std::string &name,
            int64_t interval)
            : jsRuntime_(jsRuntime), jsFunction_(jsFunction), name_(name), interval_(interval)
        {}
    ​
        ~JsTimer() = default;
    ​
        void operator()() const
        {
            if (interval_ > 0) {
                jsRuntime_.PostTask(*this, name_, interval_);
            }
    #ifdef SUPPORT_GRAPHICS
            // call js function
            ContainerScope containerScope(containerScopeId_);
    #endif
            HandleScope handleScope(jsRuntime_);
    ​
            std::vector<NativeValue*> args_;
            args_.reserve(jsArgs_.size());
            for (auto arg : jsArgs_) {
                args_.emplace_back(arg->Get());
            }
    ​
    #ifdef ENABLE_HITRACE
            TraceIdScope traceIdScope(traceId_);
    #endif
            NativeEngine& engine = jsRuntime_.GetNativeEngine();
            engine.CallFunction(engine.CreateUndefined(), jsFunction_->Get(), args_.data(), args_.size());
        }
    ​
        void PushArgs(const std::shared_ptr<NativeReference>& ref)
        {
            jsArgs_.emplace_back(ref);
        }
    ​
    private:
        JsRuntime& jsRuntime_;
        std::shared_ptr<NativeReference> jsFunction_;
        std::vector<std::shared_ptr<NativeReference>> jsArgs_;
        std::string name_;
        int64_t interval_ = 0;
    #ifdef SUPPORT_GRAPHICS
        int32_t containerScopeId_ = ContainerScope::CurrentId();
    #endif
    #ifdef ENABLE_HITRACE
        OHOS::HiviewDFX::HiTraceId traceId_ = OHOS::HiviewDFX::HiTrace::GetId();
    #endif
    };
  • 发现:每次的确有创建对象engine.CreateUndefined(),查看foundation\arkui\napi\native_engine\impl\ark\ark_native_engine_impl.cpp源码:

    NativeValue* ArkNativeEngineImpl::CreateUndefined(NativeEngine* engine)
    {
        LocalScope scope(vm_);
        Local<PrimitiveRef> value = JSValueRef::Undefined(vm_);
        return new ArkNativeValue(static_cast<ArkNativeEngine*>(engine), value);
    }
  • 继续分析:每次创建的NativeValue对象为什么不会回收

  • 查看foundation\arkui\napi\native_engine\impl\ark\native_value\ark_native_value.cpp源码:

    ArkNativeValue::ArkNativeValue(ArkNativeEngine* engine, Local<JSValueRef> value)
    {
        engine_ = engine;
        Global<JSValueRef> globalValue(engine->GetEcmaVm(), value);
        value_ = globalValue;
    ​
        NativeScopeManager* scopeManager = engine_->GetScopeManager();
        if (scopeManager != nullptr) {
            scopeManager->CreateHandle(this);
        }
    }
  • 发现:本对象指针会保存在当前scope层的新节点中

  • 查看NativeScopeManager机制

    //创建NativeScopeManager
    NativeScopeManager::NativeScopeManager()
    {
        root_ = NativeScope::CreateNewInstance();
        current_ = root_; //创建根scope,与js线程生命周期绑定
    #ifdef ENABLE_MEMLEAK_DEBUG
        if (NativeScopeManager::DEBUG_MEMLEAK != 0 && NativeScopeManager::vmas == nullptr) {
            ResetVmas(NativeScopeManager::vmas);
        }
    #endif
    }
    ​
    //NativeScopeManager进入一个新的scope层
    NativeScope* NativeScopeManager::Open()
    {
        if (current_ == nullptr) {
            HILOG_ERROR("current scope is null when open scope");
            return nullptr;
        }
    ​
        auto scope = new NativeScope(); //创建新的Scope
        if (scope != nullptr) {
            current_->child = scope; //将新建的scope挂在当前scope的子目录下
            scope->parent = current_;
            current_ = scope;//将新建的scope作为新的当前scope
        }
    ​
        return scope;
    }
    ​
    //NativeScopeManager退出一个指定的scope层
    void NativeScopeManager::Close(NativeScope* scope)
    {
        if ((scope == nullptr) || (scope == root_)) {
            return;
        }
        if (scope == current_) {
            current_ = scope->parent;//若退出当前scope层时,将其上一级scope作为新的当前scope
        }
    ​
        scope->parent->child = scope->child;
    ​
        NativeHandle* handle = scope->handlePtr;
        while (handle != nullptr) {//遍历指定scope层中缓存的所有native对象
            scope->handlePtr = handle->sibling;
            delete handle->value;//销毁缓存的native对象
            delete handle;//销毁NativeHandle对象
            handle = scope->handlePtr;
        }
        delete scope;//销毁scope对象
    }
  • 发现:NativeValue对象的回收依赖于HandleScope,而这里engine.CreateUndefined()创建的NativeValue对象作用域和handleScope不同,导致handleScope无法管控engine.CreateUndefined()方法作用域中的NativeValue对象,从而对象无法回收,应用内存持续新增

 

6 知识分享

  • napi中的NativeValue对象依赖于HandleScope的管控才能进行回收。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐