【v8】一些关于内存泄漏的踏坑

1.Persistent.Reset并非引用

v8源码:

V8_INLINE Persistent() : PersistentBase<T>(nullptr) {}


template <class T>
void PersistentBase<T>::Reset() {
  if (this->IsEmpty()) return;
  V8::DisposeGlobal(reinterpret_cast<internal::Address*>(this->val_));
  val_ = nullptr;
}


template <class T>
template <class S>
void PersistentBase<T>::Reset(Isolate* isolate, const Local<S>& other) {
  static_assert(std::is_base_of<T, S>::value, "type check");
  Reset();
  if (other.IsEmpty()) return;
  this->val_ = New(isolate, other.val_);
}


template <class T>
template <class S>
void PersistentBase<T>::Reset(Isolate* isolate,
                              const PersistentBase<S>& other) {
  static_assert(std::is_base_of<T, S>::value, "type check");
  Reset();
  if (other.IsEmpty()) return;
  this->val_ = New(isolate, other.val_);
}

所以如果代码中使用了NewPersistent.Reset(OldPersistent),要调用OldPersistent.Reset()释放掉。

2.NewInstance会导致残留Constructor,创建一个会生成两个,释放只能释放一个。

Nan::HandleScope scope;
  // Prepare constructor template
  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
  tpl->SetClassName(Nan::New(ClassName).ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);
  Nan::SetPrototypeMethod(...);
  Local<Function> cons = tpl->GetFunction();
  return Nan::NewInstance(cons).ToLocalChecked();

ps:使用heapdump库 kill -USR2 [进程号]产生的heapdump文件,第三个constructor本应与第一个对应,是100个,结果却是200个,且程序中执行清理工作时,只能释放掉100个。也就是每次NewInstance会先生成一份数据,内个我们不可控,本应作为Local类型的在作用域结束后应该释放,但并没有。之后我们通过GetReturnValue().Set(js);打进v8里是深拷贝的操作,于是形成了两个,释放也只能释放掉后面v8里的。

之前是使用了Nan库,发现新旧版本有差距

nan_maybe_43_inl.h:
inline
MaybeLocal<v8::Object> NewInstance(
      v8::Local<v8::Function> h
    , int argc
    , v8::Local<v8::Value> argv[]) {
  v8::Isolate *isolate = v8::Isolate::GetCurrent();
  v8::EscapableHandleScope scope(isolate);
  return scope.Escape(h->NewInstance(isolate->GetCurrentContext(), argc, argv)
                          .FromMaybe(v8::Local<v8::Object>()));
}
 
 
nan_maybe_pre_43_inl.h:
inline
MaybeLocal<v8::Object> NewInstance(
      v8::Local<v8::Function> h
    , int argc
    , v8::Local<v8::Value> argv[]) {
  return MaybeLocal<v8::Object>(h->NewInstance(argc, argv));
}

其中Escape 方法复制参数中的值至一个封闭的域中,然后删除其他本地句柄,最后返回这个可以被安全返回的新句柄副本。

http://www.360doc.com/content/16/0701/22/832545_572287892.shtml

在这个模型下,有一个非常常见的陷阱需要注意:你不可以直接地在一个声明了句柄域的函数中返回一个本地句柄。如果你这么做了,那么你试图返回的本地句柄,将会在函数返回之前,在句柄域的析构函数中被删除。正确的做法是使用 EscapableHandleScope 来代替 HandleScope 创建句柄域,然后调用 Escape 方法,并且传入你想要返回的句柄。

怀疑是这个操作导致的,也包括Nan库是否就是有一些问题,于是决定废弃Nan库

先尝试  cons->NewInstance(isolate->GetCurrentContext(),3,argv).ToLocalChecked();依然无效,怀疑是不是Nan的new方法构造构造FunctionTemplate出的问题。

Local<FunctionTemplate> tpg = Nan::New<FunctionTemplate>(New);

在此基础上,把FunctionTemplate改成原生方法

Local<FunctionTemplate> tpg = Nan::New<FunctionTemplate>(New); -->Local<FunctionTemplate> tpg = FunctionTemplate::New(isolate);

会提前销毁,导致使用时非法指针,可见作用域还是不一样。

重写了generateJSInstance,取消Nan库所有方法的使用,都用v8原生方法实现

 //Nan::HandleScope scope;
  Isolate *isolate = Isolate::GetCurrent();
  v8::EscapableHandleScope Escope(isolate);
  HandleScope scope(isolate);
  Local<FunctionTemplate> tpg = FunctionTemplate::New(isolate);
  Local<String> v8name=String::NewFromUtf8(isolate, ClassName.c_str(), NewStringType::kInternalized).ToLocalChecked();
  tpg->SetClassName(v8name);
  tpg->InstanceTemplate()->SetInternalFieldCount(1);
  Local<Function> cons = tpg->GetFunction();
  Local<v8::Value> argv[3] = {..., ..., ...};
  Local<Object> js;
  //js = cons->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
  //Escope.Escape没有效果
  //js = Escope.Escape(cons->NewInstance(isolate->GetCurrentContext(),3,argv).FromMaybe(v8::Local<v8::Object>()));
  js = cons->NewInstance(isolate->GetCurrentContext(),3,argv).ToLocalChecked();
  //置成SideEffectType::kHasNoSideEffect没有效果
  //js = cons->NewInstanceWithSideEffectType(isolate->GetCurrentContext(),3,argv,SideEffectType::kHasNoSideEffect).ToLocalChecked();
  args.GetReturnValue().Set(js);
  constructor.Reset();
  return ;

结果依然无效。

所以结论是还是v8原生NewInstance引发的问题 。

NewInstance会调用NewInstanceWithSideEffectType

NewInstanceWithSideEffectType源码

MaybeLocal<Object> Function::NewInstanceWithSideEffectType(
    Local<Context> context, int argc, v8::Local<v8::Value> argv[],
    SideEffectType side_effect_type) const {
  auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
  TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.Execute");
  ENTER_V8(isolate, context, Function, NewInstance, MaybeLocal<Object>(),
           InternalEscapableScope);
  i::TimerEventScope<i::TimerEventExecute> timer_scope(isolate);
  auto self = Utils::OpenHandle(this);
  STATIC_ASSERT(sizeof(v8::Local<v8::Value>) == sizeof(i::Handle<i::Object>));
  bool should_set_has_no_side_effect =
      side_effect_type == SideEffectType::kHasNoSideEffect &&
      isolate->debug_execution_mode() == i::DebugInfo::kSideEffects;
  if (should_set_has_no_side_effect) {
    CHECK(self->IsJSFunction() &&
          i::JSFunction::cast(*self).shared().IsApiFunction());
    i::Object obj =
        i::JSFunction::cast(*self).shared().get_api_func_data().call_code();
    if (obj.IsCallHandlerInfo()) {
      i::CallHandlerInfo handler_info = i::CallHandlerInfo::cast(obj);
      if (!handler_info.IsSideEffectFreeCallHandlerInfo()) {
        handler_info.SetNextCallHasNoSideEffect();
      }
    }
  }
  i::Handle<i::Object>* args = reinterpret_cast<i::Handle<i::Object>*>(argv);
  Local<Object> result;
  has_pending_exception = !ToLocal<Object>(
      i::Execution::New(isolate, self, self, argc, args), &result);
  if (should_set_has_no_side_effect) {
    i::Object obj =
        i::JSFunction::cast(*self).shared().get_api_func_data().call_code();
    if (obj.IsCallHandlerInfo()) {
      i::CallHandlerInfo handler_info = i::CallHandlerInfo::cast(obj);
      if (has_pending_exception) {
        // Restore the map if an exception prevented restoration.
        handler_info.NextCallHasNoSideEffect();
      } else {
        DCHECK(handler_info.IsSideEffectCallHandlerInfo() ||
               handler_info.IsSideEffectFreeCallHandlerInfo());
      }
    }
  }
  RETURN_ON_FAILED_EXECUTION(Object);
  RETURN_ESCAPED(result);
}

使用原生的NewInstanceWithSideEffectType置成SideEffectType::kHasNoSideEffect也没有效果,

/**
 * Options for marking whether callbacks may trigger JS-observable side effects.
 * Side-effect-free callbacks are allowlisted during debug evaluation with
 * throwOnSideEffect. It applies when calling a Function, FunctionTemplate,
 * or an Accessor callback. For Interceptors, please see
 * PropertyHandlerFlags's kHasNoSideEffect.
 * Callbacks that only cause side effects to the receiver are allowlisted if
 * invoked on receiver objects that are created within the same debug-evaluate
 * call, as these objects are temporary and the side effect does not escape.
 */
enum class SideEffectType {
  kHasSideEffect,
  kHasNoSideEffect,
  kHasSideEffectToReceiver
};

所以目前针对于这个问题,尚没有解决方案。

猜你喜欢

转载自blog.csdn.net/sm9sun/article/details/107480438