node - Buffer内存管理小结

前言

咱都知道,因为v8的限制,默认状况下64位的机器node最多使用1.4G左右的内存。若是须要使用更多的内存咋办呢? 一种办法是启动的时候经过--max-old-space-size手动把内存限制调高,但这么作的坏处是内存多了以后,每次垃圾回收的时间也会增加,这也是为啥v8默认只给1.4g的缘由。 另一种办法就是用buffer, 由于node的buffer所使用的内存放在堆外,也就没有内存的限制。node

最近也遇到须要处理文件的场景,最后使用了buffer,可是用的时候仍是有点慌,buffer的内存是怎么分配到堆外内存上的?既然是存放在堆外的内存上,那这部份内存是怎么被管理的?垃圾回收回收的时候会不会去扫这部份内存形成阻塞?git

流程

以前文章分享过,node申请内存有两种方式,github

  1. 当须要申请的内存比较大时,会直接经过native模块进行申请
  2. 从预先申请的FastBuffer池进行分配。

先来看第一种,直接分配:bash

void CreateFromString(const FunctionCallbackInfo<Value>& args) {
  CHECK(args[0]->IsString());
  CHECK(args[1]->IsString());

  enum encoding enc = ParseEncoding(args.GetIsolate(),
                                    args[1].As<String>(),
                                    UTF8);
  Local<Object> buf;

  if (New(args.GetIsolate(), args[0].As<String>(), enc).ToLocal(&buf))
    args.GetReturnValue().Set(buf);
复制代码

能够看到这里调用了New(isolate, string, enc)这个函数,函数

MaybeLocal<Object> New(Isolate* isolate,
                       Local<String> string,
                       enum encoding enc) {
  EscapableHandleScope scope(isolate);

  size_t length;
  if (!StringBytes::Size(isolate, string, enc).To(&length))
    return Local<Object>();
  size_t actual = 0;
  char* data = nullptr;

  if (length > 0) {
    data = UncheckedMalloc(length);  // 经过malloc分配内存
    ......
  }

  return scope.EscapeMaybe(New(isolate, data, actual));
}
复制代码

能够看到,这里已经经过malloc分配了内存, 而后调用了另一个new函数:post

MaybeLocal<Object> New(Isolate* isolate, char* data, size_t length) {
  EscapableHandleScope handle_scope(isolate);
  Environment* env = Environment::GetCurrent(isolate);
  if (env == nullptr) {
    free(data);
    THROW_ERR_BUFFER_CONTEXT_NOT_AVAILABLE(isolate);
    return MaybeLocal<Object>();
  }
  Local<Object> obj;
  if (Buffer::New(env, data, length, true).ToLocal(&obj))
    return handle_scope.Escape(obj);
  return Local<Object>();
}
复制代码

这里对env作了一下检测,而后调用了另一个new函数,ui

MaybeLocal<Object> New(Environment* env,
                       char* data,
                       size_t length,
                       bool uses_malloc) {
  if (length > 0) {
    CHECK_NOT_NULL(data);
    CHECK(length <= kMaxLength);
  }

  if (uses_malloc) {
    if (!env->isolate_data()->uses_node_allocator()) {
      // We don't know for sure that the allocator is malloc()-based, so we need // to fall back to the FreeCallback variant. auto free_callback = [](char* data, void* hint) { free(data); }; return New(env, data, length, free_callback, nullptr); } else { // This is malloc()-based, so we can acquire it into our own // ArrayBufferAllocator. CHECK_NOT_NULL(env->isolate_data()->node_allocator()); env->isolate_data()->node_allocator()->RegisterPointer(data, length); // 注册指针指向分配的内存 } } Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), data, length, ArrayBufferCreationMode::kInternalized); return Buffer::New(env, ab, 0, length).FromMaybe(Local<Object>()); } 复制代码

能够看到这里分了两部,第一步是在node_allocator上注册了一个指针,指向以前分配的内存,第二步生调用ArrayBuffer::New函数成了一个新的arraybuffer,this

Local<ArrayBuffer> v8::ArrayBuffer::New(
    Isolate* isolate,
    void* data,
    size_t byte_length,
    ArrayBufferCreationMode mode
) {
  // Embedders must guarantee that the external backing store is valid.
  CHECK(byte_length == 0 || data != nullptr);
  CHECK_LE(byte_length, i::JSArrayBuffer::kMaxByteLength);
  i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
  LOG_API(i_isolate, ArrayBuffer, New);
  ENTER_V8_NO_SCRIPT_NO_EXCEPTION(i_isolate);
  i::Handle<i::JSArrayBuffer> obj =
      i_isolate->factory()->NewJSArrayBuffer(i::SharedFlag::kNotShared);
  i::JSArrayBuffer::Setup(
          obj,
          i_isolate,
          mode == ArrayBufferCreationMode::kExternalized,
          data,
          byte_length
      );
  return Utils::ToLocal(obj);
}

void JSArrayBuffer::Setup(
        Handle<JSArrayBuffer> array_buffer,
        Isolate* isolate,
        bool is_external,
        void* data,
        size_t byte_length,
        SharedFlag shared_flag,
        bool is_wasm_memory
){
  DCHECK_EQ(array_buffer->GetEmbedderFieldCount(),
            v8::ArrayBuffer::kEmbedderFieldCount);
  DCHECK_LE(byte_length, JSArrayBuffer::kMaxByteLength);
  for (int i = 0; i < v8::ArrayBuffer::kEmbedderFieldCount; i++) {
    array_buffer->SetEmbedderField(i, Smi::kZero);
  }
  array_buffer->set_byte_length(byte_length);
  array_buffer->set_bit_field(0);
  array_buffer->clear_padding();
  array_buffer->set_is_external(is_external);
  array_buffer->set_is_detachable(shared_flag == SharedFlag::kNotShared);
  array_buffer->set_is_shared(shared_flag == SharedFlag::kShared);
  array_buffer->set_is_wasm_memory(is_wasm_memory);
  
  // 重点
  array_buffer->set_backing_store(data);

  if (data && !is_external) {
    isolate->heap()->RegisterNewArrayBuffer(*array_buffer);
  }
}
复制代码

能够看到,咱们传入的data被传到了set_backing_store这个方法里:spa

void JSArrayBuffer::set_backing_store(void* value, WriteBarrierMode mode) {
  intptr_t ptr = reinterpret_cast<intptr_t>(value);
  WRITE_INTPTR_FIELD(*this, kBackingStoreOffset, ptr);
}
复制代码

而这个方法看起来就是生成一个int指针,指向以前经过malloc申请的内存地址。 因此呢,看到这里前面的问题应该就很清楚了,经过这种方式申请的buffer,会在经过malloc申请一块内存,而后v8经过维护一个指向这块内存的指针来进行管理,当这个指针再也不被引用的时候,这部份内存就会被回收掉。指针

那若是是经过FastBuffer池进行分配的呢? 其实和上面的步骤差很少,由于FastBuffer实际上就是一个Uint8Array, 而v8对typedArray的处理就是自动申请一个external array buffer:

Local<Type##Array> Type##Array::New(Local<ArrayBuffer> array_buffer, 
                                      size_t byte_offset, size_t length) { 
    i::Isolate* isolate = Utils::OpenHandle(*array_buffer)->GetIsolate();  
    LOG_API(isolate, Type##Array, New); 
    ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);                              
    if (!Utils::ApiCheck(length <= kMaxLength,                             
                         "v8::" #Type 
                         "Array::New(Local<ArrayBuffer>, size_t, size_t)", 
                         "length exceeds max allowed value")) {            
      return Local<Type##Array>(); 
    }                                                                      
    i::Handle<i::JSArrayBuffer> buffer = Utils::OpenHandle(*array_buffer); 
    i::Handle<i::JSTypedArray> obj = isolate->factory()->NewJSTypedArray( 
        i::kExternal##Type##Array, buffer, byte_offset, length); 
    return Utils::ToLocal##Type##Array(obj); 
复制代码

参考

  1. node v12.0.0源码
  2. v8源码
相关文章
相关标签/搜索