Node 中 Buffer 的初始化及回收

node中的buffer相信你们都不会陌生,毕竟这个东西是node的核心之一,咱们读写文件,网络请求都会用到它。不过,以前我虽然一直在用这个东西,却没关心过他的实现,只知道经过buffer分配的内存占用的不是v8的heap上的内存,存在于newSpace和oldSpace以外,因此能够用它来进行一些大段内存的操做,可是却从没关心过它是如何分配内存,又是何时被回收这些问题。在一次有幸跟我神交已久的一位老哥的交流中,提起了这个问题才意识到本身这一块上确实存在盲区,因而专程去node源码(v8.1.4)中去寻找了一番,也算是很有所得,因此专门写一篇文章记录和分享一下。node

buffer的初始化

首先,咱们能够从lib/buffer.js中,咱们能够经过Buffer函数的代码往下追溯,发现Buffer的生成都是经过new FastBuffer来生成的,而FastBuffer咱们能够看到代码中是这样实现的:api

class FastBuffer extends Uint8Array
复制代码

这是继承自一个Uint8Array这个v8内部定义为TYPE_ARRAY的类型,从v8在v8/src/api.ccTYPED_ARRAY_NEW宏实现中咱们能够看到,相似Uint8ArrayTYPE_ARRAY都是经过ArrayBuffer来初始化的。网络

ArrayBuffer的实现

那么既然Buffer用的是v8内部的对象ArrayBuffer,那为何buffer分配的内存并不会统计到v8的heap中呢?这个问题须要咱们经过观察ArrayBuffer是如何实现的,这里咱们能够经过src/node_buffer.cc中的Buffer::New的代码来解释:函数

MaybeLocal<Object> New(Environment* env, size_t length) {
	//判断是否能生成
    ...
    data = BufferMalloc(length);

    Local<ArrayBuffer> ab =
    ArrayBuffer::New(env->isolate(),data,length,ArrayBufferCreationMode::kInternalized);
    Local<Uint8Array> ui = Uint8Array::New(ab, 0, length);
    ...
}
复制代码

从中咱们能够看到,node源码中经过BufferMalloc分配一段堆内存给初始化ArrayBuffer使用,经过分析ArrayBuffer的实现过程,咱们能够在v8/src/objects.cc中的JSArrayBuffer::Setup方法中能够看到代码:ui

array_buffer->set_backing_store(data);
复制代码

经过这个方法将指向堆内存的指针跟ArrayBuffer关联起来,放入array_buffer对象的backingstore中,因此以前的问题就已经有了答案了,buffer中所使用的内存是经过malloc这样的方式分配的堆内存,只是经过ArrayBuffer对象关联的js中使用。this

Buffer的回收

提及Buffer的回收,我相信已经有聪明的读者想到了,既然是经过js对象ArrayBuffer关联到js中使用,那确定也能经过这个对象利用v8自身的gc来进行回收。没错,对于Buffer的回收也是依赖于ArrayBuffer,在其中也是会根据ArrayBuffer所在的oldSpace和newSpace的不一样进行不一样的回收方法,不过都是经过对象ArrayBufferTracker来实现的。咱们首先来看一下newSpace中的回收方案,在v8/src/heap/heap.cc中的void Heap::Scavenge()函数,这个是作新生代GC回收的函数,在这个函数中先经过正常的GC回收方案去判断对象是否须要回收,而对于须要回收的ArrayBuffer则是经过调用:spa

ArrayBufferTracker::FreeDeadInNewSpace(this);
复制代码

来完成的,而这个函数中会轮询newSpace中全部的page,经过每一个page中的LocalArrayBufferTracker对象去轮询其中保存的每一个页中的ArrayBuffer的信息,判断是否须要清理该对象的backingStore,经过v8/src/heap/array-buffer-tracker.cc中函数:指针

template <typename Callback>
void LocalArrayBufferTracker::Process(Callback callback) {
    for (TrackingData::iterator it = array_buffers_.begin();
    it != array_buffers_.end();) {
        old_buffer = reinterpret_cast<JSArrayBuffer*>(*it);
        ...
        if (result == kKeepEntry) {
            ...
        } else if (result == kUpdateEntry) {
            ...
        } else if (result == kRemoveEntry) {
        	 //清理arrayBuffer中backingstore的内存
            freed_memory += length;
            old_buffer->FreeBackingStore();
            it = array_buffers_.erase(it);
        } 
    }
}
复制代码

而对于oldSpace中,则是经过v8/src/heap/mark-compact.cc中的函数MarkCompactCollector::Sweeper::RawSweep首先经过代码:code

const MarkingState state = MarkingState::Internal(p);
复制代码

获取page中全部对象标记状况的bitmap,接着经过该bitmap执行函数:对象

ArrayBufferTracker::FreeDead(p, state);
复制代码

经过这个函数来对page上须要释放的ArrayBuffer中的backingStore进行释放,利用也是page中的LocalArrayBufferTracker对象,经过方法:

template <typename Callback>
void LocalArrayBufferTracker::Free(Callback should_free) {
    ...
    for (TrackingData::iterator it = array_buffers_.begin();
        it != array_buffers_.end();) {
        JSArrayBuffer* buffer = reinterpret_cast<JSArrayBuffer*>(*it);
        if (should_free(buffer)) {
            freed_memory += length;
            buffer->FreeBackingStore();
            it = array_buffers_.erase(it);
        } else {
            ...
        }
    }
    ...
}
复制代码

能够看到这部分的代码跟前面几乎是同样的。

总结

经过对源码的一番窥探,咱们能够清楚的了解到了,为何buffer的内存不存在v8的heap上,并且也知道了,对于buffer中内存的释放,其释放时机的判断跟普通的js对象是同样的。读完有没有感受对buffer的使用内心有底了许多。

相关文章
相关标签/搜索