JavaScript 具備垃圾自動回收機制(Garbage Collection)簡稱 GC。垃圾回收機制會中斷整個代碼執行,釋放不可能再被使用的變量,釋放內存,這個工做機制是週期性的,咱們會在下文詳細探討。javascript
function fn1() { var obj1 = { name: 'xiaomuchen', age: '20' } } function fn2() { var obj2 = { name: 'xiaomuchen', age: '20' } return obj2 } var a = fn1() var b = fn2() console.log(a, b) // undefined, {name: "xiaomuchen", age: "20"}
咱們對比上面兩個函數,fn1 在函數內聲明變量 obj1 而且賦值,在函數執行後這個變量便不可再訪問了
,fn2 在最後把函數內的變量 obj2 返回到全局變量 b,因此 { name: 'xiaomuchen', age: '20' }
這個對象(或者說 obj2)依然可被訪問
。html
JavaScript 回收機制經過判斷變量是否可被訪問,來決定回收哪些變量。java
那麼 JavaScript 是如何判斷變量是否可被訪問?這就要提到標記清除和引用計數。node
標記清除:標記清除是目前大部分 JavaScript 引擎使用的判斷方式,經過標記變量的狀態來肯定是否可被回收。當變量在環境中被聲明時標記進入環境
,理論上永遠不要釋放進入環境的變量,由於它能夠在環境中的任何位置、任什麼時候刻被訪問。當環境被銷燬(如函數執行完),則變量被標記離開環境
等待回收。git
function fn(){ var a = { count: 10 } // 被標記,進入環境 var b = { count: 20 } // 被標記,進入環境 } fn(); // 執行完畢以後 b 被標記,離開環境
引用計數:JavaScript 引擎維護一張引用表
,保存內存中全部的資源的引用次數。資源被引用一次則引用 +1,資源被去掉引用或者退出變量的函數做用域時,則引用 -1,當資源的引用次數爲0
時,說明沒法訪問這個值,則等待回收。
(注:引用計數從 1 到 0 這個過程可能不執行,而是直接標記可被回收
,再也不進行加減運算節約開銷)github
function fn(){ var a = { count: 10 } // 資源 { count: 10 } 被引用次數爲 1 a = { count: 20 } // 資源 { count: 20 } 被引用次數爲 1,資源 { count: 10 } 被引用次數爲 0,等待回收 // do someThing } fn(); // 資源 { count: 20 } 被釋放
可是引用計數存在一種循環引用
的狀況,以下例子,兩個對象之間相互引用,在離開環境後對象不可訪問,但因爲對象的引用次數爲 1,則致使不會被回收。這個例子來自《JavaScript 高級程序設計》,但我思考良久,若是引用計數把 a.param 也做爲一個變量來計數,那麼就沒有這個問題了,引用計數實現的方式不一樣,產生的結果也不同。服務器
function fn(){ var a = { count: 10 } var b = { count: 20 } a.param = b // b 的引用次數爲 2 b.param = a // a 的引用次數爲 2 } fn(); // a、b 的引用次數爲 1
和其餘語言同樣 GC 會中斷代碼執行,中止其餘操做。由於要遍歷全部對象,回收全部不可訪問對象,這個操做的耗時可能有 100ms 以上。在 V8 引擎新版本中引入了兩種優化方法:1. 分代回收(Generation GC),2. 增量 GC(increment GC)網絡
分代回收:目的是經過對象的使用頻率、存在時長區分新生代與老生代對象。多回收新生代區(young generation),少回收老生代區(tenured generation),減小每次需遍歷的對象,從而減小每次GC的耗時閉包
增量 GC:把須要長耗時的遍歷、回收操做拆分運行
,減小中斷時間,可是會增大上下文切換開銷函數
當咱們用 Node.js 搭建一個穩定的服務時,就須要考慮服務器內存的開銷,下面一個 Node.js 內存回收執行的例子:
執行代碼node --trace_gc --trace_gc_verbose test.js
跟蹤一個網絡服務的 GC。
[41204:0x102001c00] Memory reducer: call rate 0.056, low alloc, foreground [41204:0x102001c00] Memory reducer: started GC #1 [41204:0x102001c00] Heap growing factor 1.1 based on mu=0.970, speed_ratio=42956 (gc=675253, mutator=16) [41204:0x102001c00] Grow: old size: 21382 KB, new limit: 33604 KB (1.1) [41204:0x102001c00] Memory reducer: finished GC #1 (will do more) [41204:0x102001c00] 156410 ms: Mark-sweep 27.7 (50.0) -> 21.0 (30.0) MB, 12.4 / 0.0 ms (+ 20.4 ms in 7 steps since start of marking, biggest step 4.8 ms) [Incremental marking task: finalize incremental marking] [GC in old space requested]. [41204:0x102001c00] Memory allocator, used: 30756 KB, available: 1435612 KB [41204:0x102001c00] New space, used: 169 KB, available: 838 KB, committed: 1024 KB [41204:0x102001c00] Old space, used: 16662 KB, available: 2417 KB, committed: 19412 KB [41204:0x102001c00] Code space, used: 4078 KB, available: 178 KB, committed: 5120 KB [41204:0x102001c00] Map space, used: 642 KB, available: 0 KB, committed: 2128 KB [41204:0x102001c00] Large object space, used: 0 KB, available: 1434571 KB, committed: 0 KB [41204:0x102001c00] All spaces, used: 21552 KB, available: 1438005 KB, committed: 27684 KB [41204:0x102001c00] External memory reported: 1026 KB [41204:0x102001c00] Total time spent in GC : 158.6 ms [41204:0x102001c00] Memory reducer: call rate 0.003, low alloc, foreground
首先咱們能夠看到 Node.js 區分 New space
、Old space
等來劃分檢索空間。而提示(+ 20.4 ms in 7 steps since start of marking, biggest step 4.8 ms)
告訴咱們這個標記的步驟分 7 步進行,耗時最長的一次時 4.8ms。這使 JavaScript 能夠很好的支持開發高實時應用,原文。
由於篇幅有限,留下一些小問題供你們思考:
做者:肖沐宸,github。