咱都知道,因为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);
复制代码