您好,登錄后才能下訂單哦!
這篇“Node.js中的垃圾回收機制是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Node.js中的垃圾回收機制是什么”文章吧。
GC,Garbage Collection,垃圾回收。在編程中,一般指的是內存自動回收機制,會定時將不需要用到的數據進行清除。
Node.js 底層使用了 V8 引擎。V8 是 Google 開源的一款高性能 JavaScript 引擎,使用了 C++ 進行編寫。
Node.js 的內存主要分成三部分:
代碼空間:存放代碼段的地方;
棧:函數調用棧產生的臨時變量,為一些基本類型,比如數字、字符串、布爾值,以及對象引用(保存的是地址,不保存對象本身)。
堆:存放對象等數據。
Node.js 底層使用的是 V8,下面講解一下 V8 的內存回收機制。
首先 JS 中所有的對象都會保存在堆內存中。在創建進程的時候,會分配一個初始大小的堆內存,然后我們的對象就會放到里面。
當對象越來越多,堆內存會不夠用,此時堆內存會動態地擴大。如果到達一個最大限制(現在通常是 4GB),就會堆內存溢出的錯誤,然后終止 Node.js 進程。
V8 首先將內存分成兩部分,或者說兩個生代(generation):
新生代(yong generation):保存一些存活時間較短的對象;
老生代(old generation):保存存活時間長或者長駐的對象。
新生代很小,這里會存放一些存活時間很短的對象,通常它們會被頻繁地回收(比如函數的調用棧的一些臨時對象)。
新生代可通過
node --max-semi-space-size=SIZE index.js
修改新生代的大小,單位為 MB。另外,老生代則通過
--max-old-space-size=SIZE
來設置
新生代使用了 Scavenge 算法,是一種基于 copy(復制)的算法。
新生代會分成兩個空間,這種空間稱為 semispace,它們為:
From 空間:新聲明的對象會放入這里
To 空間:用作搬移的空間
新聲明的對象會放入到 From 空間中,From 空間的對象緊密排布,通過指針,上一對象緊貼下一個對象,是內存連續的,不用擔心內存碎片的問題。
所謂內存碎片,指的是空間分配不均勻,產生大量小的連續空間,無法放入一個大對象。
當 From 空間快滿了,我們就會遍歷找出活躍對象,將它們 copy 到 To 空間。此時 From 空間其實就空了,然后我們將 From 和 To 互換身份。
如果一些對象被 copy 了多次,會被認為存活時間較長,將被移動到老生代中。
這種基于 copy 的算法,優點是可以很好地處理內存碎片的問題,缺點是會浪費一些空間作為搬移的空間位置,此外因為拷貝比較耗費時間,所以不適合分配太大的內存空間,更多是做一種輔助 GC。
老生代的空間就比新生代要大得多了,放的是一些存活時間長的對象,用的是 Mark-Sweep (標記清除)算法。
首先是標記階段。從根集 Root Set(執行棧和全局對象)往上找到所有能訪問到的對象,給它們標記為活躍對象。
標記完后,就是清除階段,將沒有標記的對象清除,其實就是標記一下這個內存地址為空閑。
這種做法會導致 空閑內存空間碎片化,當我們創建了一個大的連續對象,就會找不到地方放下。這時候,就要用 Mark-Compact(標記整理)來將碎片的活躍對象做一個整合。
Mark-Compact 會將所有活躍對象拷貝移動到一端,然后邊界的另一邊就是一整塊的連續可用內存了。
考慮到 Mark-Sweep 和 Mark-Compact 花費的時間很長,且會阻塞 JavaScript 的線程,所以通常我們不會一次性做完,而是用 增量標記 (Incremental Marking)的方式。也就是做斷斷續續地標記,小步走,垃圾回收和應用邏輯交替進行。
另外,V8 還做了并行標記和并行清理,提高執行效率。
我們可以通過 process.memoryUsage 方法拿到內存相關的一些信息。
process.memoryUsage();
輸出內容為:
說明{
rss: 35454976,
heapTotal: 7127040,
heapUsed: 5287088,
external: 958852,
arrayBuffers: 11314
}
rss:常駐內存大小(resident set size),包括代碼片段、堆內存、棧等部分。
heapTotal:V8 的堆內存總大小;
heapUsed:占用的堆內存;
external:V8 之外的的內存大小,指的是 C++ 對象占用的內存,比如 Buffer 數據。
arrayBuffers:ArrayBuffer
和 SharedArrayBuffer
相關的內存大小,屬于 external 的一部分。
以上數字的單位都是字節。
寫一個腳本,用一個定時器,讓一個數組不停地變大,并打印堆內存使用情況,直到內存溢出。
const format = function (bytes) {
return (bytes / 1024 / 1024).toFixed(2) + " MB";
};
const printMemoryUsage = function () {
const memoryUsage = process.memoryUsage();
console.log(
`heapTotal: ${format(memoryUsage.heapTotal)}, heapUsed: ${format(
memoryUsage.heapUsed
)}`
);
};
const bigArray = [];
setInterval(function () {
bigArray.push(new Array(20 * 1024 * 1024));
printMemoryUsage();
}, 500);
需要特別注意的是,不要用 Buffer 做測試。
因為 Buffer 是 Node.js 特有的處理二進制的對象,它不是在 V8 中的實現的,是 Node.js 用 C++ 另外實現的,不通過 V8 分配內存,屬于堆外內存。
我使用電腦是 macbook pro M1 Pro,Node.js 版本為 v16.17.0
,使用的 V8 版本是 9.4.146.26-node.22(通過 process.versions.v8 得到)。
輸出結果為(省略了一些多余的信息):
可以看到,是在 4000 MB 之后超出了內存上限,發生堆溢出,然后退出了進程。說明在我的機器上,默認的最大內存為 4G。heapTotal: 164.81 MB, heapUsed: 163.93 MB
heapTotal: 325.83 MB, heapUsed: 323.79 MB
heapTotal: 488.59 MB, heapUsed: 483.84 MB
...
heapTotal: 4036.44 MB, heapUsed: 4003.37 MB
heapTotal: 4196.45 MB, heapUsed: 4163.29 MB
<--- Last few GCs --->
[28033:0x140008000] 17968 ms: Mark-sweep 4003.2 (4036.4) -> 4003.1 (4036.4) MB, 2233.8 / 0.0 ms (average mu = 0.565, current mu = 0.310) allocation failure scavenge might not succeed
[28033:0x140008000] 19815 ms: Mark-sweep 4163.3 (4196.5) -> 4163.1 (4196.5) MB, 1780.3 / 0.0 ms (average mu = 0.413, current mu = 0.036) allocation failure scavenge might not succeed
<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
...
實際最大內存和它運行所在的機器有關,如果你的機器的內存大小為 2G,最大內存將設置為 1.5G。
以上就是關于“Node.js中的垃圾回收機制是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。