定时器接口内存泄漏的问题分析报告
1 关键字 setInterval;memory leak;NativeValue;HandleScope; 2 问题描述 问题现象:3.2beta2分支上,应用使用定时器setInterval,应用内存持续新增 运行环境:硬件 dayu200,软件:3.2beta2 测试步骤: 使用Deveco开发定时器demo工程。 定时器demo: @Entry @Component struct Ind
1 关键字
setInterval;memory leak;NativeValue;HandleScope;
2 问题描述
问题现象:3.2beta2分支上,应用使用定时器setInterval,应用内存持续新增
运行环境:硬件 dayu200,软件:3.2beta2
测试步骤:
-
使用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%') } }
-
安装应用,点击按钮,触发定时器。
-
通过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的管控才能进行回收。
更多推荐
所有评论(0)