自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解 Node.js 的 Buffer

開發(fā) 前端
Buffer 模塊是 Node.js 非常重要的模塊,很多模塊都依賴它,本文介紹一下 Buffer 模塊底層的原理,包括 Buffer 的核心實現(xiàn)和 V8 堆外內(nèi)存等內(nèi)容。

[[429254]]

Buffer 模塊是 Node.js 非常重要的模塊,很多模塊都依賴它,本文介紹一下 Buffer 模塊底層的原理,包括 Buffer 的核心實現(xiàn)和 V8 堆外內(nèi)存等內(nèi)容。

1 Buffer 的實現(xiàn)

1.1 Buffer 的 JS 層實現(xiàn)

Buffer 模塊的實現(xiàn)雖然非常復(fù)雜,代碼也非常多,但是很多都是編碼解碼以及內(nèi)存分配管理的邏輯,我們從常用的使用方式 Buffer.from 來看看 Buffer 的核心實現(xiàn)。

  1. Buffer.from = function from(value, encodingOrOffset, length) { 
  2.   return fromString(value, encodingOrOffset); 
  3. }; 
  4.  
  5. function fromString(string, encoding) { 
  6.   return fromStringFast(string, ops); 
  7.  
  8. function fromStringFast(string, ops) { 
  9.   const length = ops.byteLength(string); 
  10.   // 長度太長,從 C++ 層分配 
  11.   if (length >= (Buffer.poolSize >>> 1)) 
  12.     return createFromString(string, ops.encodingVal); 
  13.   // 剩下的不夠了,擴(kuò)容  
  14.   if (length > (poolSize - poolOffset)) 
  15.     createPool(); 
  16.   // 從 allocPool (ArrayBuffer)中分配內(nèi)存 
  17.   let b = new FastBuffer(allocPool, poolOffset, length); 
  18.   const actual = ops.write(b, string, 0, length); 
  19.   poolOffset += actual; 
  20.   alignPool(); 
  21.   return b; 

from 的邏輯如下:1. 如果長度大于 Node.js 設(shè)置的閾值,則調(diào)用 createFromString 通過 C++ 層直接分配內(nèi)存。2. 否則判斷之前剩下的內(nèi)存是否足夠,足夠則直接分配。Node.js 初始化時會首先分配一大塊內(nèi)存由 JS 管理,每次從這塊內(nèi)存了切分一部分給使用方,如果不夠則擴(kuò)容。我們看看 createPool。

  1. // 分配一個內(nèi)存池 
  2. function createPool() { 
  3.   poolSize = Buffer.poolSize; 
  4.   // 拿到底層的 ArrayBuffer 
  5.   allocPool = createUnsafeBuffer(poolSize).buffer; 
  6.   poolOffset = 0; 
  7.  
  8. function createUnsafeBuffer(size) { 
  9.   zeroFill[0] = 0; 
  10.   try { 
  11.     return new FastBuffer(size); 
  12.   } finally { 
  13.     zeroFill[0] = 1; 
  14.   } 
  15.  
  16. class FastBuffer extends Uint8Array {} 

我們看到最終調(diào)用 Uint8Array 實現(xiàn)了內(nèi)存分配。3. 通過 new FastBuffer(allocPool, poolOffset, length) 從內(nèi)存池中分配一塊內(nèi)存。如下圖所示。

1.2 Buffer 的 C++ 層實現(xiàn)

分析 C++ 層之前我們先看一下 V8 里下面幾個對象的關(guān)系圖。

接著來看看通過 createFromString 直接從 C++ 申請內(nèi)存的實現(xiàn)。

  1. void CreateFromString(const FunctionCallbackInfo<Value>& args) { 
  2.   enum encoding enc = static_cast<enum encoding>(args[1].As<Int32>()->Value()); 
  3.   Local<Object> buf; 
  4.   if (New(args.GetIsolate(), args[0].As<String>(), enc).ToLocal(&buf)) 
  5.     args.GetReturnValue().Set(buf); 
  6.  
  7. MaybeLocal<Object> New(Isolate* isolate, 
  8.                        Local<String> string, 
  9.                        enum encoding enc) { 
  10.   EscapableHandleScope scope(isolate); 
  11.  
  12.   size_t length; 
  13.   // 計算長度 
  14.   if (!StringBytes::Size(isolate, string, enc).To(&length)) 
  15.     return Local<Object>(); 
  16.   size_t actual = 0; 
  17.   char* data = nullptr; 
  18.   // 直接通過 realloc 在進(jìn)程堆上申請一塊內(nèi)存 
  19.   data = UncheckedMalloc(length); 
  20.   // 按照編碼轉(zhuǎn)換數(shù)據(jù) 
  21.   actual = StringBytes::Write(isolate, data, length, string, enc); 
  22.   return scope.EscapeMaybe(New(isolate, data, actual)); 
  23.  
  24. MaybeLocal<Object> New(Isolate* isolate, char* data, size_t length) { 
  25.   EscapableHandleScope handle_scope(isolate); 
  26.   Environment* env = Environment::GetCurrent(isolate); 
  27.   Local<Object> obj; 
  28.   if (Buffer::New(env, data, length).ToLocal(&obj)) 
  29.     return handle_scope.Escape(obj); 
  30.   return Local<Object>(); 
  31.  
  32. MaybeLocal<Object> New(Environment* env, 
  33.                        char* data, 
  34.                        size_t length) { 
  35.   // JS 層變量釋放后使得這塊內(nèi)存沒人用了,GC 時在回調(diào)里釋放這塊內(nèi)存                
  36.   auto free_callback = [](char* data, void* hint) { free(data); }; 
  37.   return New(env, data, length, free_callback, nullptr); 
  38.  
  39. MaybeLocal<Object> New(Environment* env, 
  40.                        char* data, 
  41.                        size_t length, 
  42.                        FreeCallback callback, 
  43.                        void* hint) { 
  44.   EscapableHandleScope scope(env->isolate()); 
  45.   // 創(chuàng)建一個 ArrayBuffer 
  46.   Local<ArrayBuffer> ab = 
  47.       CallbackInfo::CreateTrackedArrayBuffer(env, data, length, callback, hint); 
  48.   /*  
  49.     創(chuàng)建一個 Uint8Array  
  50.     Buffer::New => Local<Uint8Array> ui = Uint8Array::New(ab, byte_offset, length) 
  51.   */ 
  52.   MaybeLocal<Uint8Array> maybe_ui = Buffer::New(env, ab, 0, length); 
  53.  
  54.   Local<Uint8Array> ui; 
  55.   if (!maybe_ui.ToLocal(&ui)) 
  56.     return MaybeLocal<Object>(); 
  57.  
  58.   return scope.Escape(ui); 

通過一系列的調(diào)用,最后通過 CreateTrackedArrayBuffer 創(chuàng)建了一個 ArrayBuffer,再通過 ArrayBuffer 創(chuàng)建了一個 Uint8Array。接著看一下 CreateTrackedArrayBuffer 的實現(xiàn)。

  1. Local<ArrayBuffer> CallbackInfo::CreateTrackedArrayBuffer( 
  2.     Environment* env, 
  3.     char* data, 
  4.     size_t length, 
  5.     FreeCallback callback, 
  6.     void* hint) { 
  7.   // 管理回調(diào) 
  8.   CallbackInfo* self = new CallbackInfo(env, callback, data, hint); 
  9.   // 用自己申請的內(nèi)存創(chuàng)建一個 BackingStore,并設(shè)置 GC 回調(diào) 
  10.   std::unique_ptr<BackingStore> bs = 
  11.       ArrayBuffer::NewBackingStore(data, length, [](void*, size_t, void* arg) { 
  12.         static_cast<CallbackInfo*>(arg)->OnBackingStoreFree(); 
  13.       }, self); 
  14.   // 通過 BackingStore 創(chuàng)建 ArrayBuffer 
  15.   Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs)); 
  16.   return ab; 

看一下 NewBackingStore 的實現(xiàn)。

  1. std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStore( 
  2.     void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter, 
  3.     void* deleter_data) { 
  4.   std::unique_ptr<i::BackingStoreBase> backing_store = i::BackingStore::WrapAllocation(data, byte_length, deleter, deleter_data, 
  5.                                       i::SharedFlag::kNotShared); 
  6.   return std::unique_ptr<v8::BackingStore>( 
  7.       static_cast<v8::BackingStore*>(backing_store.release())); 
  8.  
  9. std::unique_ptr<BackingStore> BackingStore::WrapAllocation( 
  10.     void* allocation_base, size_t allocation_length, 
  11.     v8::BackingStore::DeleterCallback deleter, void* deleter_data, 
  12.     SharedFlag shared) { 
  13.   bool is_empty_deleter = (deleter == v8::BackingStore::EmptyDeleter); 
  14.   // 新建一個 BackingStore  
  15.   auto result = new BackingStore(allocation_base,    // start 
  16.                                  allocation_length,  // length 
  17.                                  allocation_length,  // capacity 
  18.                                  shared,             // shared 
  19.                                  false,              // is_wasm_memory 
  20.                                  true,               // free_on_destruct 
  21.                                  false,              // has_guard_regions 
  22.                                  // 說明釋放內(nèi)存由調(diào)用方執(zhí)行 
  23.                                  true,               // custom_deleter 
  24.                                  is_empty_deleter);  // empty_deleter 
  25.   // 保存回調(diào)需要的信息                                
  26.   result->type_specific_data_.deleter = {deleter, deleter_data}; 
  27.   return std::unique_ptr<BackingStore>(result); 

NewBackingStore 最終是創(chuàng)建了一個 BackingStore 對象。我們再看一下 GC 時 BackingStore 的析構(gòu)函數(shù)里都做了什么。

  1. BackingStore::~BackingStore() { 
  2.   if (custom_deleter_) { 
  3.     type_specific_data_.deleter.callback(buffer_start_, byte_length_, 
  4.                                          type_specific_data_.deleter.data); 
  5.     Clear(); 
  6.     return
  7.   } 

析構(gòu)的時候會執(zhí)行創(chuàng)建 BackingStore 時保存的回調(diào)。我們看一下管理回調(diào)的 CallbackInfo 的實現(xiàn)。

  1. CallbackInfo::CallbackInfo(Environment* env, 
  2.                            FreeCallback callback, 
  3.                            char* data, 
  4.                            void* hint) 
  5.     : callback_(callback), 
  6.       data_(data), 
  7.       hint_(hint), 
  8.       env_(env) { 
  9.   env->AddCleanupHook(CleanupHook, this); 
  10.   env->isolate()->AdjustAmountOfExternalAllocatedMemory(sizeof(*this)); 

CallbackInfo 的實現(xiàn)很簡單,主要的地方是 AdjustAmountOfExternalAllocatedMemory。該函數(shù)告訴 V8 堆外內(nèi)存增加了多少個字節(jié),V8 會根據(jù)內(nèi)存的數(shù)據(jù)做適當(dāng)?shù)? GC。CallbackInfo 主要是保存了回調(diào)和內(nèi)存地址。接著在 GC 的時候會回調(diào) CallbackInfo 的 OnBackingStoreFree。

  1. void CallbackInfo::OnBackingStoreFree() { 
  2.   std::unique_ptr<CallbackInfo> self { this }; 
  3.   Mutex::ScopedLock lock(mutex_); 
  4.   // check 階段執(zhí)行 CallAndResetCallback 
  5.   env_->SetImmediateThreadsafe([self = std::move(self)](Environment* env) { 
  6.     self->CallAndResetCallback(); 
  7.   });}void CallbackInfo::CallAndResetCallback() { 
  8.   FreeCallback callback; 
  9.   { 
  10.     Mutex::ScopedLock lock(mutex_); 
  11.     callback = callback_; 
  12.     callback_ = nullptr; 
  13.   } 
  14.   if (callback != nullptr) { 
  15.     // 堆外內(nèi)存減少了這么多個字節(jié) 
  16.     int64_t change_in_bytes = -static_cast<int64_t>(sizeof(*this)); 
  17.     env_->isolate()->AdjustAmountOfExternalAllocatedMemory(change_in_bytes); 
  18.     // 執(zhí)行回調(diào),通常是釋放內(nèi)存 
  19.     callback(data_, hint_); 
  20.   } 

1.3 Buffer C++ 層的另一種實現(xiàn)

剛才介紹的 C++ 實現(xiàn)中內(nèi)存是由自己分配并釋放的,下面介紹另一種內(nèi)存的分配和釋放由 V8 管理的場景。以 Buffer 的提供的 EncodeUtf8String 函數(shù)為例,該函數(shù)實現(xiàn)字符串的編碼。

  1. static void EncodeUtf8String(const FunctionCallbackInfo<Value>& args) { 
  2.   Environment* env = Environment::GetCurrent(args); 
  3.   Isolate* isolate = env->isolate(); 
  4.   // 被編碼的字符串 
  5.   Local<String> str = args[0].As<String>(); 
  6.   size_t length = str->Utf8Length(isolate); 
  7.   // 分配內(nèi)存 
  8.   AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, length); 
  9.   // 編碼 
  10.   str->WriteUtf8(isolate, 
  11.                  buf.data(), 
  12.                  -1,  // We are certain that `data` is sufficiently large 
  13.                  nullptr, 
  14.                  String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8); 
  15.   // 基于上面申請的 buf 內(nèi)存新建一個 Uint8Array               
  16.   auto array = Uint8Array::New(buf.ToArrayBuffer(), 0, length); 
  17.   args.GetReturnValue().Set(array); 

我們重點分析 AllocatedBuffer::AllocateManaged。

  1. AllocatedBuffer AllocatedBuffer::AllocateManaged( 
  2.     Environment* env, 
  3.     size_t size) { 
  4.   NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); 
  5.   std::unique_ptr<v8::BackingStore> bs = v8::ArrayBuffer::NewBackingStore(env->isolate(), size); 
  6.   return AllocatedBuffer(env, std::move(bs)); 

AllocateManaged 調(diào)用 NewBackingStore 申請了內(nèi)存。

  1. std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStore( 
  2.     Isolate* isolate, size_t byte_length) { 
  3.  
  4.   i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); 
  5.   std::unique_ptr<i::BackingStoreBase> backing_store = 
  6.       i::BackingStore::Allocate(i_isolate, byte_length, 
  7.                                 i::SharedFlag::kNotShared, 
  8.                                 i::InitializedFlag::kZeroInitialized); 
  9.  
  10.   return std::unique_ptr<v8::BackingStore>( 
  11.       static_cast<v8::BackingStore*>(backing_store.release())); 

繼續(xù)看 BackingStore::Allocate。

  1. std::unique_ptr<BackingStore> BackingStore::Allocate( 
  2.     Isolate* isolate, size_t byte_length, SharedFlag shared, 
  3.     InitializedFlag initialized) { 
  4.   void* buffer_start = nullptr; 
  5.   // ArrayBuffer 內(nèi)存分配器,可以自定義,V8 默認(rèn)提供的是使用平臺相關(guān)的堆內(nèi)存分析函數(shù),比如 malloc 
  6.   auto allocator = isolate->array_buffer_allocator(); 
  7.   if (byte_length != 0) { 
  8.     auto allocate_buffer = [allocator, initialized](size_t byte_length) { 
  9.       // 分配內(nèi)存 
  10.       void* buffer_start = allocator->Allocate(byte_length); 
  11.       return buffer_start; 
  12.     }; 
  13.     // 同步執(zhí)行 allocate_buffer 分配內(nèi)存 
  14.     buffer_start = isolate->heap()->AllocateExternalBackingStore(allocate_buffer, byte_length); 
  15.   } 
  16.   // 新建 BackingStore 管理內(nèi)存 
  17.   auto result = new BackingStore(buffer_start,  // start 
  18.                                  byte_length,   // length 
  19.                                  byte_length,   // capacity 
  20.                                  shared,        // shared 
  21.                                  false,         // is_wasm_memory 
  22.                                  true,          // free_on_destruct 
  23.                                  false,         // has_guard_regions 
  24.                                  false,         // custom_deleter 
  25.                                  false);        // empty_deleter 
  26.  
  27.   return std::unique_ptr<BackingStore>(result); 

BackingStore::Allocate 分配一塊內(nèi)存并新建 BackingStore 對象管理這塊內(nèi)存,內(nèi)存分配器是在初始化 V8 的時候設(shè)置的。這里我們再看一下 AllocateExternalBackingStore 函數(shù)的邏輯。

  1. void* Heap::AllocateExternalBackingStore( 
  2.     const std::function<void*(size_t)>& allocate, size_t byte_length) { 
  3.    // 可能需要觸發(fā) GC 
  4.    if (!always_allocate()) { 
  5.     size_t new_space_backing_store_bytes = 
  6.         new_space()->ExternalBackingStoreBytes(); 
  7.     if (new_space_backing_store_bytes >= 2 * kMaxSemiSpaceSize && 
  8.         new_space_backing_store_bytes >= byte_length) { 
  9.       CollectGarbage(NEW_SPACE, 
  10.                      GarbageCollectionReason::kExternalMemoryPressure); 
  11.     } 
  12.   } 
  13.   // 分配內(nèi)存 
  14.   void* result = allocate(byte_length); 
  15.   // 成功則返回 
  16.   if (result) return result; 
  17.   // 失敗則進(jìn)行 GC 
  18.   if (!always_allocate()) { 
  19.     for (int i = 0; i < 2; i++) { 
  20.       CollectGarbage(OLD_SPACE, 
  21.                      GarbageCollectionReason::kExternalMemoryPressure); 
  22.       result = allocate(byte_length); 
  23.       if (result) return result; 
  24.     } 
  25.     isolate()->counters()->gc_last_resort_from_handles()->Increment(); 
  26.     CollectAllAvailableGarbage( 
  27.         GarbageCollectionReason::kExternalMemoryPressure); 
  28.   } 
  29.   // 再次分配,失敗則返回失敗 
  30.   return allocate(byte_length); 

我們看到通過 BackingStore 申請內(nèi)存失敗時會觸發(fā) GC 來騰出更多的可用內(nèi)存。分配完內(nèi)存后,最終以 BackingStore 對象為參數(shù),返回一個 AllocatedBuffer 對象。

  1. AllocatedBuffer::AllocatedBuffer( 
  2.     Environment* env, std::unique_ptr<v8::BackingStore> bs) 
  3.     : env_(env), backing_store_(std::move(bs)) {} 

接著把 AllocatedBuffer 對象轉(zhuǎn)成 ArrayBuffer 對象。

  1. v8::Local<v8::ArrayBuffer> AllocatedBuffer::ToArrayBuffer() { 
  2.   return v8::ArrayBuffer::New(env_->isolate(), std::move(backing_store_)); 

最后把 ArrayBuffer 對象傳入 Uint8Array 返回一個 Uint8Array 對象返回給調(diào)用方。

2 Uint8Array 的使用和實現(xiàn)

從前面的實現(xiàn)中可以看到 C++ 層的實現(xiàn)中,內(nèi)存都是從進(jìn)程的堆中分配的,那么 JS 層通過 Uint8Array 申請的內(nèi)存是否也是在進(jìn)程堆中申請的呢?下面我們看看 V8 中 Uint8Array 的實現(xiàn)。Uint8Array 有多種創(chuàng)建方式,我們只看 new Uint8Array(length) 的實現(xiàn)。

  1. transitioning macro ConstructByLength(implicit context: Context)( 
  2.     map: Map, lengthObj: JSAny, 
  3.     elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { 
  4.   try { 
  5.     // 申請的內(nèi)存大小 
  6.     const length: uintptr = ToIndex(lengthObj); 
  7.     // 拿到創(chuàng)建 ArrayBuffer 的函數(shù) 
  8.     const defaultConstructor: Constructor = GetArrayBufferFunction(); 
  9.     const initialize: constexpr bool = true
  10.     return TypedArrayInitialize( 
  11.         initialize, map, length, elementsInfo, defaultConstructor) 
  12.         otherwise RangeError; 
  13.   } 
  14.  
  15. transitioning macro TypedArrayInitialize(implicit context: Context)( 
  16.     initialize: constexpr bool, map: Map, length: uintptr, 
  17.     elementsInfo: typed_array::TypedArrayElementsInfo, 
  18.     bufferConstructor: JSReceiver): JSTypedArray labels IfRangeError { 
  19.  
  20.   const byteLength = elementsInfo.CalculateByteLength(length); 
  21.   const byteLengthNum = Convert<Number>(byteLength); 
  22.   const defaultConstructor = GetArrayBufferFunction(); 
  23.   const byteOffset: uintptr = 0; 
  24.  
  25.   try { 
  26.     // 創(chuàng)建 JSArrayBuffer 
  27.     const buffer = AllocateEmptyOnHeapBuffer(byteLength); 
  28.     const isOnHeap: constexpr bool = true
  29.     // 通過 buffer 創(chuàng)建 TypedArray 
  30.     const typedArray = AllocateTypedArray( 
  31.         isOnHeap, map, buffer, byteOffset, byteLength, length); 
  32.     // 內(nèi)存置 0 
  33.     if constexpr (initialize) { 
  34.       const backingStore = typedArray.data_ptr; 
  35.       typed_array::CallCMemset(backingStore, 0, byteLength); 
  36.     } 
  37.  
  38.     return typedArray; 
  39.   } 

主要邏輯分為兩步,首先通過 AllocateEmptyOnHeapBuffer 申請一個 JSArrayBuffer,然后以 JSArrayBuffer 創(chuàng)建一個 TypedArray。我們先看一下 AllocateEmptyOnHeapBuffer。

  1. TNode<JSArrayBuffer> TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer( 
  2.     TNode<Context> context, TNode<UintPtrT> byte_length) { 
  3.  
  4.   TNode<NativeContext> native_context = LoadNativeContext(context); 
  5.   TNode<Map> map = CAST(LoadContextElement(native_context, Context::ARRAY_BUFFER_MAP_INDEX)); 
  6.   TNode<FixedArray> empty_fixed_array = EmptyFixedArrayConstant(); 
  7.   // 申請一個 JSArrayBuffer 對象所需要的內(nèi)存 
  8.   TNode<JSArrayBuffer> buffer = UncheckedCast<JSArrayBuffer>(Allocate(JSArrayBuffer::kSizeWithEmbedderFields)); 
  9.   // 初始化對象的屬性 
  10.   StoreMapNoWriteBarrier(buffer, map); 
  11.   StoreObjectFieldNoWriteBarrier(buffer, JSArray::kPropertiesOrHashOffset, empty_fixed_array); 
  12.   StoreObjectFieldNoWriteBarrier(buffer, JSArray::kElementsOffset, empty_fixed_array); 
  13.   int32_t bitfield_value = (1 << JSArrayBuffer::IsExternalBit::kShift) | 
  14.                            (1 << JSArrayBuffer::IsDetachableBit::kShift); 
  15.   StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldOffset, Int32Constant(bitfield_value)); 
  16.   StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kByteLengthOffset, byte_length); 
  17.   // 設(shè)置 buffer 為 nullptr                                
  18.   StoreJSArrayBufferBackingStore(buffer, EncodeExternalPointer(ReinterpretCast<RawPtrT>(IntPtrConstant(0)))); 
  19.   StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kExtensionOffset, IntPtrConstant(0)); 
  20.   for (int offset = JSArrayBuffer::kHeaderSize; offset < JSArrayBuffer::kSizeWithEmbedderFields; offset += kTaggedSize) { 
  21.     StoreObjectFieldNoWriteBarrier(buffer, offset, SmiConstant(0)); 
  22.   } 
  23.   return buffer; 

AllocateEmptyOnHeapBuffer 申請了一個空的 JSArrayBuffer 對象,空的意思是說沒有存儲數(shù)據(jù)的內(nèi)存。接著看基于 JSArrayBuffer 對象 通過 AllocateTypedArray 創(chuàng)建一個 TypedArray。

  1. transitioning macro AllocateTypedArray(implicit context: Context)( 
  2.     isOnHeap: constexpr bool, map: Map, buffer: JSArrayBuffer, 
  3.     byteOffset: uintptr, byteLength: uintptr, length: uintptr): JSTypedArray { 
  4.   // 從 V8 堆中申請存儲數(shù)據(jù)的內(nèi)存 
  5.   let elements: ByteArray = AllocateByteArray(byteLength); 
  6.   // 申請一個 JSTypedArray 對象 
  7.   const typedArray = UnsafeCast<JSTypedArray>(AllocateFastOrSlowJSObjectFromMap(map)); 
  8.   // 初始化屬性 
  9.   typedArray.elements = elements; 
  10.   typedArray.buffer = buffer; 
  11.   typedArray.byte_offset = byteOffset; 
  12.   typedArray.byte_length = byteLength; 
  13.   typedArray.length = length; 
  14.   typed_array::SetJSTypedArrayOnHeapDataPtr(typedArray, elements, byteOffset); 
  15.   SetupTypedArrayEmbedderFields(typedArray); 
  16.   return typedArray; 

我們發(fā)現(xiàn) Uint8Array 申請的內(nèi)存是基于 V8 堆的,而不是 V8 的堆外內(nèi)存,這難道和 C++ 層的實現(xiàn)不一樣?Uint8Array 的內(nèi)存的確是基于 V8 堆的,比如我像下面這樣使用的時候。

  1. const arr = new Uint8Array(1); 
  2. arr[0] = 65; 

但是如果我們使用 arr.buffer 的時候,情況就不一樣了。我們看看具體的實現(xiàn)。

  1. BUILTIN(TypedArrayPrototypeBuffer) { 
  2.   HandleScope scope(isolate); 
  3.   CHECK_RECEIVER(JSTypedArray, typed_array, 
  4.                  "get %TypedArray%.prototype.buffer"); 
  5.   return *typed_array->GetBuffer(); 

接著看 GetBuffer 的實現(xiàn)。

  1. Handle<JSArrayBuffer> JSTypedArray::GetBuffer() { 
  2.   Isolate* isolate = GetIsolate(); 
  3.   Handle<JSTypedArray> self(*this, isolate); 
  4.   // 拿到 TypeArray 對應(yīng)的 JSArrayBuffer 對象 
  5.   Handle<JSArrayBuffer> array_buffer(JSArrayBuffer::cast(self->buffer()), isolate); 
  6.   // 分配過了直接返回 
  7.   if (!is_on_heap()) { 
  8.    return array_buffer; 
  9.   } 
  10.   size_t byte_length = self->byte_length(); 
  11.   // 申請 byte_length 字節(jié)內(nèi)存存儲數(shù)據(jù) 
  12.   auto backing_store = BackingStore::Allocate(isolate, byte_length, SharedFlag::kNotShared, InitializedFlag::kUninitialized); 
  13.   // 關(guān)聯(lián) backing_store 到 array_buffer 
  14.   array_buffer->Setup(SharedFlag::kNotShared, std::move(backing_store)); 
  15.   return array_buffer; 

我們看到當(dāng)使用 buffer 的時候,V8 會在 V8 堆外申請內(nèi)存來替代初始化 Uint8Array 時在 V8 堆內(nèi)分配的內(nèi)存,并且把原來的數(shù)據(jù)復(fù)制過來??匆幌孪旅娴睦?。

  1. console.log(process.memoryUsage().arrayBuffers) 
  2. let a = new Uint8Array(10); 
  3. a[0] = 65; 
  4. console.log(process.memoryUsage().arrayBuffers) 

我們會發(fā)現(xiàn) arrayBuffers 的值是一樣的,說明 Uint8Array 初始化時沒有通過 arrayBuffers 申請堆外內(nèi)存。接著再看下一個例子。

  1. console.log(process.memoryUsage().arrayBuffers) 
  2. let a = new Uint8Array(1); 
  3. a[0] = 65; 
  4. a.buffer 
  5. console.log(process.memoryUsage().arrayBuffers) 
  6. console.log(new Uint8Array(a.buffer)) 

我們看到輸出的內(nèi)存增加了一個字節(jié),輸出的 a.buffer 是 [ 65 ](申請內(nèi)存超 64 時會從堆外申請)。

3 堆外內(nèi)存的管理

從之前的分析中我們看到,Node.js Buffer 是基于堆外內(nèi)存實現(xiàn)的(自己申請進(jìn)程堆內(nèi)存或者使用 V8 默認(rèn)的內(nèi)存分配器),我們知道,平時使用的變量都是由 V8 負(fù)責(zé)管理內(nèi)存的,那么 Buffer 所代表的堆外內(nèi)存是怎么管理的呢?Buffer 的內(nèi)存釋放也是由 V8 跟蹤的,不過釋放的邏輯和堆內(nèi)內(nèi)存不太一樣。我們通過一些例子來分析一下。

  1. function forceGC() { 
  2.     new ArrayBuffer(1024 * 1024 * 1024); 
  3.  
  4. setTimeout(() => { 
  5.     /* 
  6.         從 C++ 層調(diào)用 V8 對象創(chuàng)建內(nèi)存 
  7.         let a = process.binding('buffer').createFromString("你好", 1); 
  8.     */  
  9.     /* 
  10.         直接使用 V8 內(nèi)置對象 
  11.         let a = new ArrayBuffer(10); 
  12.     */ 
  13.     // 從 C++ 層自己管理內(nèi)存 
  14.     let a = process.binding('buffer').encodeUtf8String("你好"); 
  15.     // 置空等待 GC 
  16.     a = null
  17.     // 分配一塊大內(nèi)存觸發(fā) GC 
  18.     process.nextTick(forceGC); 
  19. }, 1000); 
  20.  
  21. const net = require('net'); 
  22. net.createServer((socket) => {}).listen() 

在 V8 的代碼打斷點,然后調(diào)試以上代碼。

我們看到在超時回調(diào)里 V8 分配了一個 ArrayBufferExtension 對象并記錄到 ArrayBufferSweeper 中。接著看一下觸發(fā) GC 時的邏輯。

V8 在 GC 中會調(diào)用

heap_->array_buffer_sweeper()->RequestSweepYoung() 回收堆外內(nèi)存,另外 Node.js 本身似乎也使用線程去回收 堆外內(nèi)存。我們再看一下自己管理內(nèi)存的情況下回調(diào)的觸發(fā)。

如果這樣寫是不會觸發(fā) BackingStore::~BackingStore 執(zhí)行的,再次驗證了 Uint8Array 初始化時沒有使用 BackingStore。

  1. setTimeout(() => { 
  2.    let a = new Uint8Array(1); 
  3.    // a.buffer; 
  4.    a = null
  5.    process.nextTick(forceGC); 
  6. }); 

但是如果把注釋打開就可以。

4 總結(jié)

Buffer 平時用起來可能比較簡單,但是如果深入研究它的實現(xiàn)就會發(fā)現(xiàn)涉及的內(nèi)容不僅多,而且還復(fù)雜,不過深入理解了它的底層實現(xiàn)后,會有種豁然開朗的感覺,另外 Buffer 的內(nèi)存是堆外內(nèi)存,如果我們發(fā)現(xiàn)進(jìn)程的內(nèi)存不斷增長但是 V8 堆快照大小變化不大,那可能是 Buffer 變量沒有釋放,理解實現(xiàn)能幫助我們更好地思考問題和解決問題。

 

 

 

作者

 

責(zé)任編輯:武曉燕 來源: 編程雜技
相關(guān)推薦

2021-08-26 13:57:56

Node.jsEncodingBuffer

2021-08-05 05:46:06

Node.jsInspector工具

2021-08-12 01:00:29

NodejsAsync

2021-09-01 13:32:48

Node.jsAPI POSIX

2021-09-10 06:50:03

Node.jsSocket端口

2013-11-01 09:34:56

Node.js技術(shù)

2019-08-15 14:42:24

進(jìn)程線程javascript

2013-06-14 09:27:51

Express.jsJavaScript

2021-05-27 09:00:00

Node.js開發(fā)線程

2024-01-05 08:49:15

Node.js異步編程

2015-07-16 09:59:55

PHP Node.js討論

2017-08-16 10:36:10

JavaScriptNode.js事件驅(qū)動

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過濾器

2015-03-10 10:59:18

Node.js開發(fā)指南基礎(chǔ)介紹

2020-08-31 15:00:17

Node.jsrequire前端

2020-05-29 15:33:28

Node.js框架JavaScript

2012-11-22 10:11:16

LispLisp教程

2021-12-25 22:29:57

Node.js 微任務(wù)處理事件循環(huán)
點贊
收藏

51CTO技術(shù)棧公眾號