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源碼
相關文章
相關標籤/搜索