node中的buffer相信你們都不會陌生,畢竟這個東西是node的核心之一,咱們讀寫文件,網絡請求都會用到它。不過,以前我雖然一直在用這個東西,卻沒關心過他的實現,只知道經過buffer分配的內存佔用的不是v8的heap上的內存,存在於newSpace和oldSpace以外,因此能夠用它來進行一些大段內存的操做,可是卻從沒關心過它是如何分配內存,又是何時被回收這些問題。在一次有幸跟我神交已久的一位老哥的交流中,提起了這個問題才意識到本身這一塊上確實存在盲區,因而專程去node源碼(v8.1.4)中去尋找了一番,也算是很有所得,因此專門寫一篇文章記錄和分享一下。node
首先,咱們能夠從lib/buffer.js
中,咱們能夠經過Buffer函數的代碼往下追溯,發現Buffer的生成都是經過new FastBuffer
來生成的,而FastBuffer咱們能夠看到代碼中是這樣實現的:api
class FastBuffer extends Uint8Array
複製代碼
這是繼承自一個Uint8Array
這個v8內部定義爲TYPE_ARRAY
的類型,從v8在v8/src/api.cc
的TYPED_ARRAY_NEW
宏實現中咱們能夠看到,相似Uint8Array
的TYPE_ARRAY
都是經過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的回收,我相信已經有聰明的讀者想到了,既然是經過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的使用內心有底了許多。