深入理解 Node.js 的 Buffer
Buffer 模塊是 Node.js 非常重要的模塊,很多模塊都依賴它,本文介紹一下 Buffer 模塊底層的原理,包括 Buffer 的核心實(shí)現(xiàn)和 V8 堆外內(nèi)存等內(nèi)容。
1 Buffer 的實(shí)現(xiàn)
1.1 Buffer 的 JS 層實(shí)現(xiàn)
Buffer 模塊的實(shí)現(xiàn)雖然非常復(fù)雜,代碼也非常多,但是很多都是編碼解碼以及內(nèi)存分配管理的邏輯,我們從常用的使用方式 Buffer.from 來看看 Buffer 的核心實(shí)現(xiàn)。
- Buffer.from = function from(value, encodingOrOffset, length) {
 - return fromString(value, encodingOrOffset);
 - };
 - function fromString(string, encoding) {
 - return fromStringFast(string, ops);
 - }
 - function fromStringFast(string, ops) {
 - const length = ops.byteLength(string);
 - // 長(zhǎng)度太長(zhǎng),從 C++ 層分配
 - if (length >= (Buffer.poolSize >>> 1))
 - return createFromString(string, ops.encodingVal);
 - // 剩下的不夠了,擴(kuò)容
 - if (length > (poolSize - poolOffset))
 - createPool();
 - // 從 allocPool (ArrayBuffer)中分配內(nèi)存
 - let b = new FastBuffer(allocPool, poolOffset, length);
 - const actual = ops.write(b, string, 0, length);
 - poolOffset += actual;
 - alignPool();
 - return b;
 - }
 
from 的邏輯如下:1. 如果長(zhǎng)度大于 Node.js 設(shè)置的閾值,則調(diào)用 createFromString 通過 C++ 層直接分配內(nèi)存。2. 否則判斷之前剩下的內(nèi)存是否足夠,足夠則直接分配。Node.js 初始化時(shí)會(huì)首先分配一大塊內(nèi)存由 JS 管理,每次從這塊內(nèi)存了切分一部分給使用方,如果不夠則擴(kuò)容。我們看看 createPool。
- // 分配一個(gè)內(nèi)存池
 - function createPool() {
 - poolSize = Buffer.poolSize;
 - // 拿到底層的 ArrayBuffer
 - allocPool = createUnsafeBuffer(poolSize).buffer;
 - poolOffset = 0;
 - }
 - function createUnsafeBuffer(size) {
 - zeroFill[0] = 0;
 - try {
 - return new FastBuffer(size);
 - } finally {
 - zeroFill[0] = 1;
 - }
 - }
 - class FastBuffer extends Uint8Array {}
 
我們看到最終調(diào)用 Uint8Array 實(shí)現(xiàn)了內(nèi)存分配。3. 通過 new FastBuffer(allocPool, poolOffset, length) 從內(nèi)存池中分配一塊內(nèi)存。如下圖所示。
1.2 Buffer 的 C++ 層實(shí)現(xiàn)
分析 C++ 層之前我們先看一下 V8 里下面幾個(gè)對(duì)象的關(guān)系圖。
接著來看看通過 createFromString 直接從 C++ 申請(qǐng)內(nèi)存的實(shí)現(xiàn)。
- void CreateFromString(const FunctionCallbackInfo<Value>& args) {
 - enum encoding enc = static_cast<enum encoding>(args[1].As<Int32>()->Value());
 - Local<Object> buf;
 - if (New(args.GetIsolate(), args[0].As<String>(), enc).ToLocal(&buf))
 - args.GetReturnValue().Set(buf);
 - }
 - MaybeLocal<Object> New(Isolate* isolate,
 - Local<String> string,
 - enum encoding enc) {
 - EscapableHandleScope scope(isolate);
 - size_t length;
 - // 計(jì)算長(zhǎng)度
 - if (!StringBytes::Size(isolate, string, enc).To(&length))
 - return Local<Object>();
 - size_t actual = 0;
 - char* data = nullptr;
 - // 直接通過 realloc 在進(jìn)程堆上申請(qǐng)一塊內(nèi)存
 - data = UncheckedMalloc(length);
 - // 按照編碼轉(zhuǎn)換數(shù)據(jù)
 - actual = StringBytes::Write(isolate, data, length, string, enc);
 - return scope.EscapeMaybe(New(isolate, data, actual));
 - }
 - MaybeLocal<Object> New(Isolate* isolate, char* data, size_t length) {
 - EscapableHandleScope handle_scope(isolate);
 - Environment* env = Environment::GetCurrent(isolate);
 - Local<Object> obj;
 - if (Buffer::New(env, data, length).ToLocal(&obj))
 - return handle_scope.Escape(obj);
 - return Local<Object>();
 - }
 - MaybeLocal<Object> New(Environment* env,
 - char* data,
 - size_t length) {
 - // JS 層變量釋放后使得這塊內(nèi)存沒人用了,GC 時(shí)在回調(diào)里釋放這塊內(nèi)存
 - auto free_callback = [](char* data, void* hint) { free(data); };
 - return New(env, data, length, free_callback, nullptr);
 - }
 - MaybeLocal<Object> New(Environment* env,
 - char* data,
 - size_t length,
 - FreeCallback callback,
 - void* hint) {
 - EscapableHandleScope scope(env->isolate());
 - // 創(chuàng)建一個(gè) ArrayBuffer
 - Local<ArrayBuffer> ab =
 - CallbackInfo::CreateTrackedArrayBuffer(env, data, length, callback, hint);
 - /*
 - 創(chuàng)建一個(gè) Uint8Array
 - Buffer::New => Local<Uint8Array> ui = Uint8Array::New(ab, byte_offset, length)
 - */
 - MaybeLocal<Uint8Array> maybe_ui = Buffer::New(env, ab, 0, length);
 - Local<Uint8Array> ui;
 - if (!maybe_ui.ToLocal(&ui))
 - return MaybeLocal<Object>();
 - return scope.Escape(ui);
 - }
 
通過一系列的調(diào)用,最后通過 CreateTrackedArrayBuffer 創(chuàng)建了一個(gè) ArrayBuffer,再通過 ArrayBuffer 創(chuàng)建了一個(gè) Uint8Array。接著看一下 CreateTrackedArrayBuffer 的實(shí)現(xiàn)。
- Local<ArrayBuffer> CallbackInfo::CreateTrackedArrayBuffer(
 - Environment* env,
 - char* data,
 - size_t length,
 - FreeCallback callback,
 - void* hint) {
 - // 管理回調(diào)
 - CallbackInfo* self = new CallbackInfo(env, callback, data, hint);
 - // 用自己申請(qǐng)的內(nèi)存創(chuàng)建一個(gè) BackingStore,并設(shè)置 GC 回調(diào)
 - std::unique_ptr<BackingStore> bs =
 - ArrayBuffer::NewBackingStore(data, length, [](void*, size_t, void* arg) {
 - static_cast<CallbackInfo*>(arg)->OnBackingStoreFree();
 - }, self);
 - // 通過 BackingStore 創(chuàng)建 ArrayBuffer
 - Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
 - return ab;
 - }
 
看一下 NewBackingStore 的實(shí)現(xiàn)。
- std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStore(
 - void* data, size_t byte_length, v8::BackingStore::DeleterCallback deleter,
 - void* deleter_data) {
 - std::unique_ptr<i::BackingStoreBase> backing_store = i::BackingStore::WrapAllocation(data, byte_length, deleter, deleter_data,
 - i::SharedFlag::kNotShared);
 - return std::unique_ptr<v8::BackingStore>(
 - static_cast<v8::BackingStore*>(backing_store.release()));
 - }
 - std::unique_ptr<BackingStore> BackingStore::WrapAllocation(
 - void* allocation_base, size_t allocation_length,
 - v8::BackingStore::DeleterCallback deleter, void* deleter_data,
 - SharedFlag shared) {
 - bool is_empty_deleter = (deleter == v8::BackingStore::EmptyDeleter);
 - // 新建一個(gè) BackingStore
 - auto result = new BackingStore(allocation_base, // start
 - allocation_length, // length
 - allocation_length, // capacity
 - shared, // shared
 - false, // is_wasm_memory
 - true, // free_on_destruct
 - false, // has_guard_regions
 - // 說明釋放內(nèi)存由調(diào)用方執(zhí)行
 - true, // custom_deleter
 - is_empty_deleter); // empty_deleter
 - // 保存回調(diào)需要的信息
 - result->type_specific_data_.deleter = {deleter, deleter_data};
 - return std::unique_ptr<BackingStore>(result);
 - }
 
NewBackingStore 最終是創(chuàng)建了一個(gè) BackingStore 對(duì)象。我們?cè)倏匆幌?GC 時(shí) BackingStore 的析構(gòu)函數(shù)里都做了什么。
- BackingStore::~BackingStore() {
 - if (custom_deleter_) {
 - type_specific_data_.deleter.callback(buffer_start_, byte_length_,
 - type_specific_data_.deleter.data);
 - Clear();
 - return;
 - }
 - }
 
析構(gòu)的時(shí)候會(huì)執(zhí)行創(chuàng)建 BackingStore 時(shí)保存的回調(diào)。我們看一下管理回調(diào)的 CallbackInfo 的實(shí)現(xiàn)。
- CallbackInfo::CallbackInfo(Environment* env,
 - FreeCallback callback,
 - char* data,
 - void* hint)
 - : callback_(callback),
 - data_(data),
 - hint_(hint),
 - env_(env) {
 - env->AddCleanupHook(CleanupHook, this);
 - env->isolate()->AdjustAmountOfExternalAllocatedMemory(sizeof(*this));
 - }
 
CallbackInfo 的實(shí)現(xiàn)很簡(jiǎn)單,主要的地方是 AdjustAmountOfExternalAllocatedMemory。該函數(shù)告訴 V8 堆外內(nèi)存增加了多少個(gè)字節(jié),V8 會(huì)根據(jù)內(nèi)存的數(shù)據(jù)做適當(dāng)?shù)? GC。CallbackInfo 主要是保存了回調(diào)和內(nèi)存地址。接著在 GC 的時(shí)候會(huì)回調(diào) CallbackInfo 的 OnBackingStoreFree。
- void CallbackInfo::OnBackingStoreFree() {
 - std::unique_ptr<CallbackInfo> self { this };
 - Mutex::ScopedLock lock(mutex_);
 - // check 階段執(zhí)行 CallAndResetCallback
 - env_->SetImmediateThreadsafe([self = std::move(self)](Environment* env) {
 - self->CallAndResetCallback();
 - });}void CallbackInfo::CallAndResetCallback() {
 - FreeCallback callback;
 - {
 - Mutex::ScopedLock lock(mutex_);
 - callback = callback_;
 - callback_ = nullptr;
 - }
 - if (callback != nullptr) {
 - // 堆外內(nèi)存減少了這么多個(gè)字節(jié)
 - int64_t change_in_bytes = -static_cast<int64_t>(sizeof(*this));
 - env_->isolate()->AdjustAmountOfExternalAllocatedMemory(change_in_bytes);
 - // 執(zhí)行回調(diào),通常是釋放內(nèi)存
 - callback(data_, hint_);
 - }
 - }
 
1.3 Buffer C++ 層的另一種實(shí)現(xiàn)
剛才介紹的 C++ 實(shí)現(xiàn)中內(nèi)存是由自己分配并釋放的,下面介紹另一種內(nèi)存的分配和釋放由 V8 管理的場(chǎng)景。以 Buffer 的提供的 EncodeUtf8String 函數(shù)為例,該函數(shù)實(shí)現(xiàn)字符串的編碼。
- static void EncodeUtf8String(const FunctionCallbackInfo<Value>& args) {
 - Environment* env = Environment::GetCurrent(args);
 - Isolate* isolate = env->isolate();
 - // 被編碼的字符串
 - Local<String> str = args[0].As<String>();
 - size_t length = str->Utf8Length(isolate);
 - // 分配內(nèi)存
 - AllocatedBuffer buf = AllocatedBuffer::AllocateManaged(env, length);
 - // 編碼
 - str->WriteUtf8(isolate,
 - buf.data(),
 - -1, // We are certain that `data` is sufficiently large
 - nullptr,
 - String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);
 - // 基于上面申請(qǐng)的 buf 內(nèi)存新建一個(gè) Uint8Array
 - auto array = Uint8Array::New(buf.ToArrayBuffer(), 0, length);
 - args.GetReturnValue().Set(array);
 - }
 
我們重點(diǎn)分析 AllocatedBuffer::AllocateManaged。
- AllocatedBuffer AllocatedBuffer::AllocateManaged(
 - Environment* env,
 - size_t size) {
 - NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
 - std::unique_ptr<v8::BackingStore> bs = v8::ArrayBuffer::NewBackingStore(env->isolate(), size);
 - return AllocatedBuffer(env, std::move(bs));
 - }
 
AllocateManaged 調(diào)用 NewBackingStore 申請(qǐng)了內(nèi)存。
- std::unique_ptr<v8::BackingStore> v8::ArrayBuffer::NewBackingStore(
 - Isolate* isolate, size_t byte_length) {
 - i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
 - std::unique_ptr<i::BackingStoreBase> backing_store =
 - i::BackingStore::Allocate(i_isolate, byte_length,
 - i::SharedFlag::kNotShared,
 - i::InitializedFlag::kZeroInitialized);
 - return std::unique_ptr<v8::BackingStore>(
 - static_cast<v8::BackingStore*>(backing_store.release()));
 - }
 
繼續(xù)看 BackingStore::Allocate。
- std::unique_ptr<BackingStore> BackingStore::Allocate(
 - Isolate* isolate, size_t byte_length, SharedFlag shared,
 - InitializedFlag initialized) {
 - void* buffer_start = nullptr;
 - // ArrayBuffer 內(nèi)存分配器,可以自定義,V8 默認(rèn)提供的是使用平臺(tái)相關(guān)的堆內(nèi)存分析函數(shù),比如 malloc
 - auto allocator = isolate->array_buffer_allocator();
 - if (byte_length != 0) {
 - auto allocate_buffer = [allocator, initialized](size_t byte_length) {
 - // 分配內(nèi)存
 - void* buffer_start = allocator->Allocate(byte_length);
 - return buffer_start;
 - };
 - // 同步執(zhí)行 allocate_buffer 分配內(nèi)存
 - buffer_start = isolate->heap()->AllocateExternalBackingStore(allocate_buffer, byte_length);
 - }
 - // 新建 BackingStore 管理內(nèi)存
 - auto result = new BackingStore(buffer_start, // start
 - byte_length, // length
 - byte_length, // capacity
 - shared, // shared
 - false, // is_wasm_memory
 - true, // free_on_destruct
 - false, // has_guard_regions
 - false, // custom_deleter
 - false); // empty_deleter
 - return std::unique_ptr<BackingStore>(result);
 - }
 
BackingStore::Allocate 分配一塊內(nèi)存并新建 BackingStore 對(duì)象管理這塊內(nèi)存,內(nèi)存分配器是在初始化 V8 的時(shí)候設(shè)置的。這里我們?cè)倏匆幌?AllocateExternalBackingStore 函數(shù)的邏輯。
- void* Heap::AllocateExternalBackingStore(
 - const std::function<void*(size_t)>& allocate, size_t byte_length) {
 - // 可能需要觸發(fā) GC
 - if (!always_allocate()) {
 - size_t new_space_backing_store_bytes =
 - new_space()->ExternalBackingStoreBytes();
 - if (new_space_backing_store_bytes >= 2 * kMaxSemiSpaceSize &&
 - new_space_backing_store_bytes >= byte_length) {
 - CollectGarbage(NEW_SPACE,
 - GarbageCollectionReason::kExternalMemoryPressure);
 - }
 - }
 - // 分配內(nèi)存
 - void* result = allocate(byte_length);
 - // 成功則返回
 - if (result) return result;
 - // 失敗則進(jìn)行 GC
 - if (!always_allocate()) {
 - for (int i = 0; i < 2; i++) {
 - CollectGarbage(OLD_SPACE,
 - GarbageCollectionReason::kExternalMemoryPressure);
 - result = allocate(byte_length);
 - if (result) return result;
 - }
 - isolate()->counters()->gc_last_resort_from_handles()->Increment();
 - CollectAllAvailableGarbage(
 - GarbageCollectionReason::kExternalMemoryPressure);
 - }
 - // 再次分配,失敗則返回失敗
 - return allocate(byte_length);
 - }
 
我們看到通過 BackingStore 申請(qǐng)內(nèi)存失敗時(shí)會(huì)觸發(fā) GC 來騰出更多的可用內(nèi)存。分配完內(nèi)存后,最終以 BackingStore 對(duì)象為參數(shù),返回一個(gè) AllocatedBuffer 對(duì)象。
- AllocatedBuffer::AllocatedBuffer(
 - Environment* env, std::unique_ptr<v8::BackingStore> bs)
 - : env_(env), backing_store_(std::move(bs)) {}
 
接著把 AllocatedBuffer 對(duì)象轉(zhuǎn)成 ArrayBuffer 對(duì)象。
- v8::Local<v8::ArrayBuffer> AllocatedBuffer::ToArrayBuffer() {
 - return v8::ArrayBuffer::New(env_->isolate(), std::move(backing_store_));
 - }
 
最后把 ArrayBuffer 對(duì)象傳入 Uint8Array 返回一個(gè) Uint8Array 對(duì)象返回給調(diào)用方。
2 Uint8Array 的使用和實(shí)現(xiàn)
從前面的實(shí)現(xiàn)中可以看到 C++ 層的實(shí)現(xiàn)中,內(nèi)存都是從進(jìn)程的堆中分配的,那么 JS 層通過 Uint8Array 申請(qǐng)的內(nèi)存是否也是在進(jìn)程堆中申請(qǐng)的呢?下面我們看看 V8 中 Uint8Array 的實(shí)現(xiàn)。Uint8Array 有多種創(chuàng)建方式,我們只看 new Uint8Array(length) 的實(shí)現(xiàn)。
- transitioning macro ConstructByLength(implicit context: Context)(
 - map: Map, lengthObj: JSAny,
 - elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray {
 - try {
 - // 申請(qǐng)的內(nèi)存大小
 - const length: uintptr = ToIndex(lengthObj);
 - // 拿到創(chuàng)建 ArrayBuffer 的函數(shù)
 - const defaultConstructor: Constructor = GetArrayBufferFunction();
 - const initialize: constexpr bool = true;
 - return TypedArrayInitialize(
 - initialize, map, length, elementsInfo, defaultConstructor)
 - otherwise RangeError;
 - }
 - }
 - transitioning macro TypedArrayInitialize(implicit context: Context)(
 - initialize: constexpr bool, map: Map, length: uintptr,
 - elementsInfo: typed_array::TypedArrayElementsInfo,
 - bufferConstructor: JSReceiver): JSTypedArray labels IfRangeError {
 - const byteLength = elementsInfo.CalculateByteLength(length);
 - const byteLengthNum = Convert<Number>(byteLength);
 - const defaultConstructor = GetArrayBufferFunction();
 - const byteOffset: uintptr = 0;
 - try {
 - // 創(chuàng)建 JSArrayBuffer
 - const buffer = AllocateEmptyOnHeapBuffer(byteLength);
 - const isOnHeap: constexpr bool = true;
 - // 通過 buffer 創(chuàng)建 TypedArray
 - const typedArray = AllocateTypedArray(
 - isOnHeap, map, buffer, byteOffset, byteLength, length);
 - // 內(nèi)存置 0
 - if constexpr (initialize) {
 - const backingStore = typedArray.data_ptr;
 - typed_array::CallCMemset(backingStore, 0, byteLength);
 - }
 - return typedArray;
 - }
 - }
 
主要邏輯分為兩步,首先通過 AllocateEmptyOnHeapBuffer 申請(qǐng)一個(gè) JSArrayBuffer,然后以 JSArrayBuffer 創(chuàng)建一個(gè) TypedArray。我們先看一下 AllocateEmptyOnHeapBuffer。
- TNode<JSArrayBuffer> TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer(
 - TNode<Context> context, TNode<UintPtrT> byte_length) {
 - TNode<NativeContext> native_context = LoadNativeContext(context);
 - TNode<Map> map = CAST(LoadContextElement(native_context, Context::ARRAY_BUFFER_MAP_INDEX));
 - TNode<FixedArray> empty_fixed_array = EmptyFixedArrayConstant();
 - // 申請(qǐng)一個(gè) JSArrayBuffer 對(duì)象所需要的內(nèi)存
 - TNode<JSArrayBuffer> buffer = UncheckedCast<JSArrayBuffer>(Allocate(JSArrayBuffer::kSizeWithEmbedderFields));
 - // 初始化對(duì)象的屬性
 - StoreMapNoWriteBarrier(buffer, map);
 - StoreObjectFieldNoWriteBarrier(buffer, JSArray::kPropertiesOrHashOffset, empty_fixed_array);
 - StoreObjectFieldNoWriteBarrier(buffer, JSArray::kElementsOffset, empty_fixed_array);
 - int32_t bitfield_value = (1 << JSArrayBuffer::IsExternalBit::kShift) |
 - (1 << JSArrayBuffer::IsDetachableBit::kShift);
 - StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldOffset, Int32Constant(bitfield_value));
 - StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kByteLengthOffset, byte_length);
 - // 設(shè)置 buffer 為 nullptr
 - StoreJSArrayBufferBackingStore(buffer, EncodeExternalPointer(ReinterpretCast<RawPtrT>(IntPtrConstant(0))));
 - StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kExtensionOffset, IntPtrConstant(0));
 - for (int offset = JSArrayBuffer::kHeaderSize; offset < JSArrayBuffer::kSizeWithEmbedderFields; offset += kTaggedSize) {
 - StoreObjectFieldNoWriteBarrier(buffer, offset, SmiConstant(0));
 - }
 - return buffer;
 - }
 
AllocateEmptyOnHeapBuffer 申請(qǐng)了一個(gè)空的 JSArrayBuffer 對(duì)象,空的意思是說沒有存儲(chǔ)數(shù)據(jù)的內(nèi)存。接著看基于 JSArrayBuffer 對(duì)象 通過 AllocateTypedArray 創(chuàng)建一個(gè) TypedArray。
- transitioning macro AllocateTypedArray(implicit context: Context)(
 - isOnHeap: constexpr bool, map: Map, buffer: JSArrayBuffer,
 - byteOffset: uintptr, byteLength: uintptr, length: uintptr): JSTypedArray {
 - // 從 V8 堆中申請(qǐng)存儲(chǔ)數(shù)據(jù)的內(nèi)存
 - let elements: ByteArray = AllocateByteArray(byteLength);
 - // 申請(qǐng)一個(gè) JSTypedArray 對(duì)象
 - const typedArray = UnsafeCast<JSTypedArray>(AllocateFastOrSlowJSObjectFromMap(map));
 - // 初始化屬性
 - typedArray.elements = elements;
 - typedArray.buffer = buffer;
 - typedArray.byte_offset = byteOffset;
 - typedArray.byte_length = byteLength;
 - typedArray.length = length;
 - typed_array::SetJSTypedArrayOnHeapDataPtr(typedArray, elements, byteOffset);
 - SetupTypedArrayEmbedderFields(typedArray);
 - return typedArray;
 
我們發(fā)現(xiàn) Uint8Array 申請(qǐng)的內(nèi)存是基于 V8 堆的,而不是 V8 的堆外內(nèi)存,這難道和 C++ 層的實(shí)現(xiàn)不一樣?Uint8Array 的內(nèi)存的確是基于 V8 堆的,比如我像下面這樣使用的時(shí)候。
- const arr = new Uint8Array(1);
 - arr[0] = 65;
 
但是如果我們使用 arr.buffer 的時(shí)候,情況就不一樣了。我們看看具體的實(shí)現(xiàn)。
- BUILTIN(TypedArrayPrototypeBuffer) {
 - HandleScope scope(isolate);
 - CHECK_RECEIVER(JSTypedArray, typed_array,
 - "get %TypedArray%.prototype.buffer");
 - return *typed_array->GetBuffer();
 - }
 
接著看 GetBuffer 的實(shí)現(xiàn)。
- Handle<JSArrayBuffer> JSTypedArray::GetBuffer() {
 - Isolate* isolate = GetIsolate();
 - Handle<JSTypedArray> self(*this, isolate);
 - // 拿到 TypeArray 對(duì)應(yīng)的 JSArrayBuffer 對(duì)象
 - Handle<JSArrayBuffer> array_buffer(JSArrayBuffer::cast(self->buffer()), isolate);
 - // 分配過了直接返回
 - if (!is_on_heap()) {
 - return array_buffer;
 - }
 - size_t byte_length = self->byte_length();
 - // 申請(qǐng) byte_length 字節(jié)內(nèi)存存儲(chǔ)數(shù)據(jù)
 - auto backing_store = BackingStore::Allocate(isolate, byte_length, SharedFlag::kNotShared, InitializedFlag::kUninitialized);
 - // 關(guān)聯(lián) backing_store 到 array_buffer
 - array_buffer->Setup(SharedFlag::kNotShared, std::move(backing_store));
 - return array_buffer;
 - }
 
我們看到當(dāng)使用 buffer 的時(shí)候,V8 會(huì)在 V8 堆外申請(qǐng)內(nèi)存來替代初始化 Uint8Array 時(shí)在 V8 堆內(nèi)分配的內(nèi)存,并且把原來的數(shù)據(jù)復(fù)制過來。看一下下面的例子。
- console.log(process.memoryUsage().arrayBuffers)
 - let a = new Uint8Array(10);
 - a[0] = 65;
 - console.log(process.memoryUsage().arrayBuffers)
 
我們會(huì)發(fā)現(xiàn) arrayBuffers 的值是一樣的,說明 Uint8Array 初始化時(shí)沒有通過 arrayBuffers 申請(qǐng)堆外內(nèi)存。接著再看下一個(gè)例子。
- console.log(process.memoryUsage().arrayBuffers)
 - let a = new Uint8Array(1);
 - a[0] = 65;
 - a.buffer
 - console.log(process.memoryUsage().arrayBuffers)
 - console.log(new Uint8Array(a.buffer))
 
我們看到輸出的內(nèi)存增加了一個(gè)字節(jié),輸出的 a.buffer 是 [ 65 ](申請(qǐng)內(nèi)存超 64 時(shí)會(huì)從堆外申請(qǐng))。
3 堆外內(nèi)存的管理
從之前的分析中我們看到,Node.js Buffer 是基于堆外內(nèi)存實(shí)現(xiàn)的(自己申請(qǐng)進(jìn)程堆內(nèi)存或者使用 V8 默認(rèn)的內(nèi)存分配器),我們知道,平時(shí)使用的變量都是由 V8 負(fù)責(zé)管理內(nèi)存的,那么 Buffer 所代表的堆外內(nèi)存是怎么管理的呢?Buffer 的內(nèi)存釋放也是由 V8 跟蹤的,不過釋放的邏輯和堆內(nèi)內(nèi)存不太一樣。我們通過一些例子來分析一下。
- function forceGC() {
 - new ArrayBuffer(1024 * 1024 * 1024);
 - }
 - setTimeout(() => {
 - /*
 - 從 C++ 層調(diào)用 V8 對(duì)象創(chuàng)建內(nèi)存
 - let a = process.binding('buffer').createFromString("你好", 1);
 - */
 - /*
 - 直接使用 V8 內(nèi)置對(duì)象
 - let a = new ArrayBuffer(10);
 - */
 - // 從 C++ 層自己管理內(nèi)存
 - let a = process.binding('buffer').encodeUtf8String("你好");
 - // 置空等待 GC
 - a = null;
 - // 分配一塊大內(nèi)存觸發(fā) GC
 - process.nextTick(forceGC);
 - }, 1000);
 - const net = require('net');
 - net.createServer((socket) => {}).listen()
 
在 V8 的代碼打斷點(diǎn),然后調(diào)試以上代碼。
我們看到在超時(shí)回調(diào)里 V8 分配了一個(gè) ArrayBufferExtension 對(duì)象并記錄到 ArrayBufferSweeper 中。接著看一下觸發(fā) GC 時(shí)的邏輯。
V8 在 GC 中會(huì)調(diào)用
heap_->array_buffer_sweeper()->RequestSweepYoung() 回收堆外內(nèi)存,另外 Node.js 本身似乎也使用線程去回收 堆外內(nèi)存。我們?cè)倏匆幌伦约汗芾韮?nèi)存的情況下回調(diào)的觸發(fā)。
如果這樣寫是不會(huì)觸發(fā) BackingStore::~BackingStore 執(zhí)行的,再次驗(yàn)證了 Uint8Array 初始化時(shí)沒有使用 BackingStore。
- setTimeout(() => {
 - let a = new Uint8Array(1);
 - // a.buffer;
 - a = null;
 - process.nextTick(forceGC);
 - });
 
但是如果把注釋打開就可以。
4 總結(jié)
Buffer 平時(shí)用起來可能比較簡(jiǎn)單,但是如果深入研究它的實(shí)現(xiàn)就會(huì)發(fā)現(xiàn)涉及的內(nèi)容不僅多,而且還復(fù)雜,不過深入理解了它的底層實(shí)現(xiàn)后,會(huì)有種豁然開朗的感覺,另外 Buffer 的內(nèi)存是堆外內(nèi)存,如果我們發(fā)現(xiàn)進(jìn)程的內(nèi)存不斷增長(zhǎng)但是 V8 堆快照大小變化不大,那可能是 Buffer 變量沒有釋放,理解實(shí)現(xiàn)能幫助我們更好地思考問題和解決問題。
作者





















 
 
 




 
 
 
 