亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Node - 內(nèi)存管理和垃圾回收

joyqi / 1632人閱讀

摘要:的內(nèi)存限制和垃圾回收機制內(nèi)存限制內(nèi)存限制一般的后端語言開發(fā)中,在基本的內(nèi)存使用是沒有限制的。的內(nèi)存分代目前沒有一種垃圾自動回收算法適用于所有場景,所以的內(nèi)部采用的其實是兩種垃圾回收算法。

前言

從前端思維轉(zhuǎn)變到后端, 有一個很重要的點就是內(nèi)存管理。以前寫前端因為只是在瀏覽器上運行, 所以對于內(nèi)存管理一般不怎么需要上心, 但是在服務(wù)器端, 則需要斤斤計較內(nèi)存。

V8的內(nèi)存限制和垃圾回收機制 內(nèi)存限制

內(nèi)存限制
一般的后端語言開發(fā)中, 在基本的內(nèi)存使用是沒有限制的。 但由于Node是基于V8構(gòu)建的, 而V8對于內(nèi)存的使用有一定的限制。 在默認情況下, 64位的機器大概可以使用1.4G, 而32則為0.7G的大小。關(guān)于為什么要限制內(nèi)存大小, 有兩個方面。一個是V8一開始是為瀏覽器服務(wù)的, 而在瀏覽器端這樣的內(nèi)存大小是綽綽有余的。另一個則是待會提到的垃圾回收機制, 垃圾回收會暫停Js的運行, 如果內(nèi)存過大, 就會導(dǎo)致垃圾回收的時間變長, 從而導(dǎo)致Js暫停的時間過長。

當然, 我們可以在啟動Node服務(wù)的時候, 手動設(shè)置內(nèi)存的大小 如下:

node --max-old-space-size=768 // 設(shè)置老生代, 單位為MB  
node --max-semi-space-size=64 // 設(shè)置新生代, 單位為MB

查看內(nèi)存
在Node環(huán)境中, 可以通過process.memoryUsage()來查看內(nèi)存分配

rss(resident set size):所有內(nèi)存占用,包括指令區(qū)和堆棧

heapTotal:V8引擎可以分配的最大堆內(nèi)存,包含下面的 heapUsed

heapUsed:V8引擎已經(jīng)分配使用的堆內(nèi)存

external: V8管理C++對象綁定到JavaScript對象上的內(nèi)存

事實上, 對于大文件的操作通常會使用Buffer, 究其原因就是因為Node中內(nèi)存小的原因, 而使用Buffer是不受這個限制, 它是堆外內(nèi)存, 也就是上面提到的external。

v8的內(nèi)存分代

目前沒有一種垃圾自動回收算法適用于所有場景, 所以v8的內(nèi)部采用的其實是兩種垃圾回收算法。他們回收的對象分別是生存周期較短和生存周期較長的兩種對象。關(guān)于具體的算法, 參考下文。 這里先介紹v8是怎么做內(nèi)存分代的。

新生代
v8中的新生代主要存放的是生存周期較短的對象, 它具有兩個空間semispace, 分別為From和To, 在分配內(nèi)存的時候?qū)?nèi)存分配給From空間, 當垃圾回收的時候, 會檢查From空間存活的對象(廣度優(yōu)先算法)并復(fù)制到To空間, 然后清空From空間, 再互相交換From和To空間的位置, 使得To空間變?yōu)镕rom空間

該算法缺陷很明顯就是有一半的空間一直閑置著并且需要復(fù)制對象, 但是由于新生代本身具有的內(nèi)存比較小加上其分配的對象都是生存周期比較短的對象, 所以浪費的空間以及復(fù)制使用的開銷會比較小。

在64位系統(tǒng)中一個semisapce為16MB, 而32位則為8MB, 所以新生代內(nèi)存大小分別為32MB和16MB

老生代
老生代主要存放的是生存周期比較長的對象。內(nèi)存按照 1MB 分頁,并且都按照 1MB 對齊。新生代的內(nèi)存頁是連續(xù)的,而老生代的內(nèi)存頁是分散的,以鏈表的形式串聯(lián)起來。 它的內(nèi)部有4種類型。

Old Space
Old Space 保存的是老生代里的普通對象(在 V8 中指的是 Old Object Space,與保存對象結(jié)構(gòu)的 Map Space 和保存編譯出的代碼的 Code Space 相對),這些對象大部分是從新生代(即 New Space)晉升而來。

Large Object Space
當 V8 需要分配一個 1MB 的頁(減去 header)無法直接容納的對象時,就會直接在 Large Object Space 而不是 New Space 分配。在垃圾回收時,Large Object Space 里的對象不會被移動或者復(fù)制(因為成本太高)。Large Object Space 屬于老生代,使用 Mark-Sweep-Compact 回收內(nèi)存。

Map Space
所有在堆上分配的對象都帶有指向它的“隱藏類”的指針,這些“隱藏類”是 V8 根據(jù)運行時的狀態(tài)記錄下的對象布局結(jié)構(gòu),用于快速訪問對象成員,而這些“隱藏類”(Map)就保存在 Map Space。

Code Space
編譯器針對運行平臺架構(gòu)編譯出的機器碼(存儲在可執(zhí)行內(nèi)存中)本身也是數(shù)據(jù),連同一些其它的元數(shù)據(jù)(比如由哪個編譯器編譯,源代碼的位置等),放置在 Code Space 中。

關(guān)于Map Space和Code Space推薦大家看這兩篇文章, 因為和本文關(guān)系不大, 所以不在這里贅述。 文章1文章2

v8的內(nèi)存分配如下圖, 圖出處:

V8的垃圾回收機制

新生代
新生代采用Scavenge垃圾回收算法,在算法實現(xiàn)時主要采用Cheney算法。關(guān)于算法的實現(xiàn)在上面中已經(jīng)大致說明了, 但新生代的對象是怎么晉升到老生代里面呢?

在默認情況下,V8的對象分配主要集中在From空間中。對象從From空間中復(fù)制到To空間時,會檢查它的內(nèi)存地址來判斷這個對象是否已經(jīng)經(jīng)歷過一次Scavenge回收。如果已經(jīng)經(jīng)歷過了,會將該對象從From空間復(fù)制到老生代空間中,如果沒有,則復(fù)制到To空間中。這個晉升流程如下圖所示

另一個判斷條件是To空間的內(nèi)存占用比。當要從From空間復(fù)制一個對象到To空間時,如果To空間已經(jīng)使用了超過25%,則這個對象直接晉升到老生代空間中,這個晉升的判斷示意圖如下圖所示。

寫屏障
關(guān)于新生代掃描的問題, 由于我們想回收的是新生代的對象, 那么只需檢查指向新生代的引用, 那么在跟隨根對象->新生代或者新生代->新生代的引用時, 那么掃描會很快。 但是還可能出現(xiàn)的一種情況是老生代指向了新生代或者指向了根對象, 如果選擇跟隨, 掃描整個堆, 就會花費太多時間。

對于這個問題,V8 選擇的解決方案是使用寫屏障(write barrier),即每次往一個對象寫入一個指針(添加引用)的時候,都執(zhí)行一段代碼,這段代碼會檢查這個被寫入的指針是否是由老生代對象指向新生代對象的,這樣我們就能明確地記錄下所有從老生代指向新生代的指針了。這個用于記錄的數(shù)據(jù)結(jié)構(gòu)叫做 store buffer,每個堆維護一個,為了防止它無限增長下去,會定期地進行清理、去重和更新。這樣,我們可以通過掃描,得知根對象->新生代和新生代->新生代的引用,通過檢查 store buffer,得知老生代->新生代的引用,就沒有漏網(wǎng)之魚,可以安心地對新生代進行回收了。

新生代GC圖:

老生代
老生代在64位和32位下具有的內(nèi)存分別是1400MB和700MB, 如果還使用新生代的Scavenge算法, 不止浪費一半空間, 還需要復(fù)制大塊內(nèi)存。所以, V8在老生代中的垃圾回收策略采用Mark-Sweep和Mark-Compact相結(jié)合。

Mark-Sweep(標記清除)
標記清除分為標記和清除兩個階段。在標記階段需要遍歷堆中的所有對象,并標記那些活著的對象,然后進入清除階段。在清除階段總,只清除沒有被標記的對象。由于標記清除只清除死亡對象,而死亡對象在老生代中占用的比例很小,所以效率較高

標記清除有一個問題就是進行一次標記清楚后,內(nèi)存空間往往是不連續(xù)的,會出現(xiàn)很多的內(nèi)存碎片。如果后續(xù)需要分配一個需要內(nèi)存空間較多的對象時,如果所有的內(nèi)存碎片都不夠用,將會使得V8無法完成這次分配,提前觸發(fā)垃圾回收。

圖中黑色部分為標記的死亡對象

Mark-Compact(標記整理)
標記整理正是為了解決標記清除所帶來的內(nèi)存碎片的問題。標記整理在標記清除的基礎(chǔ)進行修改,將其的清除階段變?yōu)榫o縮極端。在整理的過程中,將活著的對象向內(nèi)存區(qū)的一段移動,移動完成后直接清理掉邊界外的內(nèi)存。緊縮過程涉及對象的移動,所以效率并不是太好,但是能保證不會生成內(nèi)存碎片

由于標記整理需要移動對象, 所以它的速度相對較慢。 V8在主要使用標記清除算法, 在空間不足以分配新生代晉升的對象時才使用標記整理算法。

白色格子為存活對象,深色格子為死亡對象,淺色格子為存活對象移動后留下的空洞

關(guān)于標記的具體算法, 如果將對中的對象看做由指針做邊的有向圖,標記算法的核心就是深度優(yōu)先搜索。
V8使用每個對象的兩個mark-bits和一個標記工作棧來實現(xiàn)標記,兩個mark-bits編碼三種顏色:白色(00),灰色(10)和黑色(11)。

白色: 表示對象可以回收

黑色: 表示對象不可以回收,并且他的所有引用都被便利完畢了

灰色: 表示對象不可回收,他的引用對象沒有掃描完畢。

當老生代GC啟動時, V8會掃描老生代的對象, 并對其進行標記。 大致的流程如下:

將所有非根對象標記為白色。

將根的所有直接引用對象入棧,并標記為灰色(marking worklist)

從這些對象開始做深度優(yōu)先搜索,每訪問一個對象,就將它 pop 出來,標記為黑色,然后將它引用的所有白色對象標記為灰色,push 到棧上

棧空的時候,回收白色的對象

但這里需要留意一下, 當對象太大無法 push 進空間有限的棧的時候,V8 會先把這個對象保留灰色放棄掉,然后將整個棧標記為溢出狀態(tài)(overflowed)。在溢出狀態(tài)下,V8 會繼續(xù)從棧上 pop 對象,標記為黑色,再將引用的白色對象標記為灰色和溢出,但不會將這些灰色的對象 push 到棧上去。這樣沒多久,棧上的所有對象都被標黑清空了。此時 V8 開始遍歷整個堆,把那些同時標記為灰色和溢出對象按照老方法標記完。由于溢出后需要額外掃描一遍堆(如果發(fā)生多次溢出還可能掃描多遍),當程序創(chuàng)建了太多大對象的時候,就會顯著影響 GC 的效率。 引用自文章  
增量標記與惰性清理
事實上, v8為了降低全堆垃圾回收帶來的停頓時間, 使用了增量標記和惰性清理兩種方式。

增量標記
將原本要一口氣停頓完成的動作改為增量標記(incremental marking),也就是拆分為許多小“步進”,每做完一“步進”就讓JavaScript應(yīng)用邏輯執(zhí)行一小會兒,垃圾回收與應(yīng)用邏輯交替執(zhí)行直到標記階段完成。

因為增量標記的過程中, 很有可能被標記為白色的對象又被重新引用, 所以需要一個寫屏障(write-barrier)來實現(xiàn)通知。

// Called after `object.field = value`.
write_barrier(object, field_offset, value) {
  if (color(object) == black && color(value) == white) {
    set_color(value, grey);
    marking_worklist.push(value);
  }
}

下圖為增量標記示意圖。

惰性清理
所有的對象已被處理,因此非死即活,堆上多少空間可以變?yōu)榭臻e已經(jīng)成為定局。此時我們可以不急著釋放那些空間,而將清理的過程延遲一下也并無大礙。因此無需一次清理所有的頁,垃圾回收器會視需要逐一進行清理,直到所有的頁都清理完畢。

Orinoco

V8將新一代的GC稱為Orinoco, 在Orinoco下, GC的算法更加高效。

Orinoco 新生代
關(guān)于Orinoco在新生代中, 其實比較容易理解, 因為它只是增加了幾個worker線程來幫助處理, 如圖:

Orinoco 老生代

并行標記 parallel marking

并行標記是標記由主線程和工作線程進行, 程序會阻塞

其數(shù)據(jù)結(jié)構(gòu)如圖所示:

Marking worklist負責(zé)決定分給其他worker thread的工作量,決定了性能與保持本地線程的均衡,V8使用基于內(nèi)存段的方式去平衡各個線程的工作量,避免線程同步的耗時與盡可能的工作。即將內(nèi)存分為一段段給每個線程工作。

并發(fā)標記 Concurrent marking

并發(fā)標記是由工作線程進行標記, 主線程繼續(xù)運行, 程序不會阻塞

并發(fā)標記允許標記行為與應(yīng)用程序同時進行,很可能發(fā)生數(shù)據(jù)競爭, 所以main thread需要與worker threads在發(fā)生數(shù)據(jù)競爭時進行同步,大多數(shù)的數(shù)據(jù)競爭行為通過輕量級的原子級內(nèi)存訪問就可以同步,但是一些特殊的場景需要獨占整個對象的訪問。V8是利用一個Bailout worklist來處理被獨占的整個對象, 并由主線程處理, 如圖:

合并
基于并行標記和并發(fā)標記, v8最后的垃圾回收機制如圖:

其步驟如下:

從root對象開始掃描,填充對象到marking worklist

分布并發(fā)標記任務(wù)到worker threads

worker threads 通過合作耗盡marking worklist來幫助main threads 更快地完成標記。

有時候, main threads也會通過處理bailout worklist和marking worklist參與標記。

如果marking worklist為空, 則主線程完成垃圾回收

在結(jié)束之前,main thread重新掃描roots,可能會發(fā)現(xiàn)其他的白色節(jié)點,這些白色節(jié)點會在worker threads的幫助下,被平行標記

準確式GC

提到GC不得不提一下準確式GC, 這個也是V8引擎效率比較高的原因, 以下引用自文章

雖然 ECMAScript 中沒有規(guī)定整數(shù)類型,Number 都是 IEEE 浮點數(shù),但是由于在 CPU 上浮點數(shù)相關(guān)的操作通常比整型操作要慢,大多數(shù)的 JavaScript 引擎都在底層實現(xiàn)中引入了整型,用于提升 for 循環(huán)和數(shù)組索引等場景的性能,并配以一定的技巧來將指針和整數(shù)(可能還有浮點數(shù))“壓縮”到同一種數(shù)據(jù)結(jié)構(gòu)中節(jié)省空間。  

在 V8 中,對象都按照 4 字節(jié)(32 位機器)或者 8 字節(jié)(64 位機器)對齊,因此對象的地址都能被 4 或者 8 整除,這意味著地址的二進制表示最后 2 位或者 3 位都會是 0,也就是說所有指針的這幾位是可以空出來使用的。如果將另一種類型的數(shù)據(jù)的最后一位也保留出來另作他用,就可以通過判斷最后一位是 0 還是 1,來直接分辨兩種類型。那么,這另一種類型的數(shù)據(jù)就可以直接塞在前面幾位,而不需要沿著一個指針去讀取它的實際內(nèi)容。在 V8 的語境內(nèi)這種結(jié)構(gòu)叫做小整數(shù)(SMI, small integer),這是語言實現(xiàn)中歷史悠久的常用技巧 tagging 的一種。V8 預(yù)留所有的字(word,32位機器是 4 字節(jié),64 位機器是 8 字節(jié))的最后一位用于標記(tag)這個字中的內(nèi)容的類型,1 表示指針,0 表示整數(shù),這樣給定一個內(nèi)存中的字,它能通過查看最后一位快速地判斷它包含的指針還是整數(shù),并且可以將整數(shù)直接存儲在字中,無需先通過一個指針間接引用過來,節(jié)省空間。

由于 V8 能夠通過查看字的最后一位,快速地分辨指針和整數(shù),在 GC 的時候,V8 能夠跳過所有的整數(shù),更快地沿著指針掃描堆中的對象。由于在 GC 的過程中,V8 能夠準確地分辨它所遍歷到的每一塊內(nèi)存的內(nèi)容屬于什么類型,因此 V8 的垃圾回收器是準確式的。與此相對的是保守式 GC,即垃圾回收器因為某些設(shè)計導(dǎo)致無法確定內(nèi)存中內(nèi)容的類型,只能保守地先假設(shè)它們都是指針然后再加以驗證,以免誤回收不該回收的內(nèi)存,因此可能誤將數(shù)據(jù)當作指針,進而誤以為一些對象仍然被引用,無法回收而浪費內(nèi)存。同時因為保守式的垃圾回收器沒有十足的把握區(qū)分指針和數(shù)據(jù),也就不能確保自己能安全地修改指針,無法使用那些需要移動對象,更新指針的算法。

內(nèi)存觀察&GC日志

GC日志
范例中的圖片來自:Are your v8 garbage collection logs speaking to you?Joyee Cheung -Alibaba Cloud(Alibaba Group)

option

--trace_gc

--trace_gc_nvp

--trace_gc_verbose

內(nèi)存觀察
內(nèi)存觀察這一塊需要借助第三方工具, 因為一些原因個人只是在開發(fā)和測試階段開啟了easy-monitor觀察是否內(nèi)存泄漏, 再使用heapdump + chrome dev tools來定位具體的泄漏原因。其實業(yè)內(nèi)最好的還是接入alinode, 但是公司接入的困難度比較高, 原因大家都懂的啦~

另外推薦一些這方面不錯的資料:
《Node.js 調(diào)試指南》
關(guān)于Nodejs性能監(jiān)控思考

還有就是一些可能造成內(nèi)存泄漏的代碼(這里就不貼代碼了, 網(wǎng)上例子會更詳細):

全局變量

閉包(包括commonjs規(guī)范, 其實質(zhì)是一個閉包生成)

緩存

總結(jié)

關(guān)于內(nèi)存和GC, 相應(yīng)在編碼的時候需要考慮的細節(jié)和客戶端不同, 需要比較謹慎的為每一份資源做出安排。

參考

V8 —— 你需要知道的垃圾回收機制
聊聊V8引擎的垃圾回收
淺談V8引擎中的垃圾回收機制
解讀 V8 GC Log(一): Node.js 應(yīng)用背景與 GC 基礎(chǔ)知識
解讀 V8 GC Log(二): 堆內(nèi)外內(nèi)存的劃分與 GC 算法
Orinoco: young generation garbage collection
Concurrent marking in V8
V8 之旅: 垃圾回收器
Are your v8 garbage collection logs speaking to you?Joyee Cheung -Alibaba Cloud(Alibaba Group)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/104105.html

相關(guān)文章

  • Node.js內(nèi)存管理V8垃圾回收機制

    摘要:垃圾回收內(nèi)存管理實踐先通過一個來看看在中進行垃圾回收的過程是怎樣的內(nèi)存泄漏識別在環(huán)境里提供了方法用來查看當前進程內(nèi)存使用情況,單位為字節(jié)中保存的進程占用的內(nèi)存部分,包括代碼本身棧堆。 showImg(https://segmentfault.com/img/remote/1460000019894672?w=640&h=426);作者 | 五月君Node.js 技術(shù)棧 | https:...

    JowayYoung 評論0 收藏0
  • 【譯文】Node.js垃圾回收機制-基礎(chǔ)

    摘要:正好最近在學(xué)習(xí)的各種實現(xiàn)原理,在這里斗膽翻譯一篇垃圾回收機制原文鏈接。自動管理的機制中,通常都會包含垃圾回收機制。二垃圾回收機制的概念垃圾回收,是一種自動管理應(yīng)用程序所占內(nèi)存的機制,簡稱方便起見,本文均采用此簡寫。 最近關(guān)注了一個國外技術(shù)博客RisingStack里面有很多高質(zhì)量,且對新手也很friendly的文章。正好最近在學(xué)習(xí)Node.js的各種實現(xiàn)原理,在這里斗膽翻譯一篇Node...

    haobowd 評論0 收藏0
  • 深入淺出 JavaScript 內(nèi)存管理垃圾回收

    摘要:可以改成下面代碼手動移除事件監(jiān)聽器和變量例四清除定時器清除變量鏈接觀察垃圾回收是怎么工作的在上面圖片中,可以觀察到,點擊按鈕,內(nèi)存和節(jié)點數(shù)暴增,當點擊時,垃圾收集器回收了這些定時器變量等,從而釋放了內(nèi)存。 簡介 本篇文章講解JavaScript 中垃圾回收機制,內(nèi)存泄漏,結(jié)合一些常遇到的例子,相信各位看完后,會對JS 中垃圾回收機制有個深入的了解。 我的github,歡迎 star 內(nèi)...

    oogh 評論0 收藏0
  • JavaScript 垃圾回收

    摘要:根據(jù)的定義,垃圾回收是一種自動的內(nèi)存管理機制。但在沒有結(jié)束前,回調(diào)函數(shù)里的變量以及回調(diào)函數(shù)本身都無法被回收。在內(nèi)存泄漏部分,我們討論了無意的全局變量會帶來無法回收的內(nèi)存垃圾。 根據(jù) Wiki 的定義,垃圾回收是一種自動的內(nèi)存管理機制。當計算機上的動態(tài)內(nèi)存不再需要時,就應(yīng)該予以釋放,以讓出內(nèi)存。直白點講,就是程序是運行在內(nèi)存里的,當聲明一個變量、定義一個函數(shù)時都會占用內(nèi)存。內(nèi)存的容量是有...

    BothEyes1993 評論0 收藏0
  • 淺談V8引擎中的垃圾回收機制

    摘要:新生代的對象為存活時間較短的對象,老生代中的對象為存活時間較長或常駐內(nèi)存的對象。分別對新生代和老生代使用不同的垃圾回收算法來提升垃圾回收的效率。如果指向老生代我們就不必考慮它了。 這篇文章的所有內(nèi)容均來自 樸靈的《深入淺出Node.js》及A tour of V8:Garbage Collection,后者還有中文翻譯版V8 之旅: 垃圾回收器,我在這里只是做了個記錄和結(jié)合 垃圾回收...

    happen 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<