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的使用內心有底了許多。

相關文章
相關標籤/搜索