咱都知道,因爲v8的限制,默認狀況下64位的機器node最多使用1.4G左右的內存。若是須要使用更多的內存咋辦呢? 一種辦法是啓動的時候經過--max-old-space-size手動把內存限制調高,但這麼作的壞處是內存多了以後,每次垃圾回收的時間也會增加,這也是爲啥v8默認只給1.4g的緣由。 另一種辦法就是用buffer, 由於node的buffer所使用的內存放在堆外,也就沒有內存的限制。node
最近也遇到須要處理文件的場景,最後使用了buffer,可是用的時候仍是有點慌,buffer的內存是怎麼分配到堆外內存上的?既然是存放在堆外的內存上,那這部份內存是怎麼被管理的?垃圾回收回收的時候會不會去掃這部份內存形成阻塞?git
以前文章分享過,node申請內存有兩種方式,github
先來看第一種,直接分配: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);
複製代碼