JavaScript GC簡書

1.垃圾回收算法

垃圾:沒法再被訪問的對象或內存空間
延遲:指平均每次垃圾回收開始到結束須要的時間。
吞吐量:指平均必定時間內能回收多少內存,內存多少這個概念很是普遍,能夠指多少個對象,也能夠指多少字節的空間,具體的應該看指標應需求而異。
根節點:如全局變量上的對對象的引用、棧上對對象的引用等用戶必定可以訪問到的地址,是尋找活對象的入口。javascript

下面簡單地介紹引用計數、Mark-Sweep、Mark-Copy、Mark-Compact四種垃圾回收算法java

1.1 引用計數node

這是最初級的垃圾收集算法。此算法把「對象是否再也不須要」簡化定義爲「對象有沒有其餘對象引用到它」。若是沒有引用指向該對象(零引用),對象將被垃圾回收機制回收。咱們能夠爲每一個對象都增長一個計數器,來記錄對這個對象的引用數量,當引用計數歸零時,這個對象變成了垃圾算法

引用計數的優勢以下:緩存

(1)內存釋放及時,當一個對象死亡時其佔用的內存立刻被釋放
(2)延遲低,內存釋放的時間均勻地分佈在各個時間段

缺點以下:閉包

(1)每一個對象須要附帶一個計數字段的空間
(2)引用複製和銷燬時須要對改變計數字段,這可能涉及到相對昂貴的原子操做
(3)沒法處理循環引用,好比兩個對象互相引用對方的狀況

進階話題:當一個大對象的引用歸零時,經常會致使一大批的對象引用歸零,這種成批釋放的狀況很是常見,會致使垃圾回收的延遲上升以及可能佔用大量棧空間去遞歸釋放循環引用能夠經過一些算法檢測到,也能夠在適當時刻使用其餘垃圾回收算法來釋放引用計數器的更新是存在冗餘的,即一大部分的引用計數的更新是能夠被消除的dom

1.2 標記清除 Mark-Sweep性能

除去引用計數,Mark-Sweep是另外一個思考方向。它分爲**標記和清除**兩個階段。當垃圾回收被觸發時,運行時從有限的根節點(在Javascript裏,根是全局對象)出發,對全部可以到達的對象進行標記(通常爲深度優先搜索),而後再遍歷整個堆,清除全部未被標記的對象。spa

通常認爲其優勢有:code

(1)相比引用計數很難處理循環引用,Mark-Sweep算法總能找到全部沒法被引用的對象
(2)因爲垃圾總被一塊兒批量回收,可能能夠提升內存回收的吞吐
(3)這個算法實現起來簡單

缺點以下:

(1)每一個對象須要附帶至少一個比特做爲標記的空間
(2)因爲Mark階段須要在整個堆上隨機遍歷,對CPU緩存不友好
(3)算法的性能與堆的大小相關,當堆很是大,而單次回收對象數量有限時,性能被嚴重拖累
(4)垃圾回收的延遲較高,會使用戶代碼徹底中止一段時間
(5)出現內存不連續的狀態

進階話題:增量標記,即經過對算法必定的修改,Mark階段能夠與用戶程序交替執行直到標記階段完成,以減小垃圾回收算法的延遲。

1.3 標記複製 Mark-Copy

Mark-Copy將堆內存一分爲二,一個處於使用狀態,一個處於閒置狀態。當開始垃圾回收時,會檢查使用狀態的內存塊,把存活的對象複製到閒置狀態的內存塊,完成複製後,兩個內存空間交換角色。

相較於Mark-Sweep,其優勢有:

(1)在回收垃圾的同時也整理內存,避免了內存碎片化的問題
(2)非侵入式的算法,不須要對象上的字段(理想是美好的,但現實每每不是)
(3)算法的執行時間僅與活對象的數量有關,不須要掃描整個堆
(4)分配對象時不須要尋找空閒空間,由於其總在當前使用的堆的末尾

缺點以下:

(1)回收時須要進行大量的內存拷貝
(2)內存利用率低,維護了兩個堆,卻只用了一半的空間

進階話題:
經過分塊的方式維護N個堆,以提升內存利用率
對活對象進行分代維護

1.4 標記整理 Mark-Compact

注意Mark-Copy算法須要維護一個額外的堆來做爲拷貝活對象的容器。標記整理和標記清除的差異在於對象標記死亡後,在整理內存的過程當中,將活着的對象往一端移動,移動完成後,直接清理邊界外的內存。

能夠說Mark-Compact是Mark-Copy和Mark-Sweep算法的一種整合,其優缺點也只是前兩種算法各取部分。

標記清除,標記複製,標記整理特色

(1)標記清除只複製活着的對象,用空間換取時間,速度最快
(2)標記複製只清除死亡的對象
(3)標記整理是二者的整合,速度最慢


2.V8 內存管理和垃圾回收機制

不一樣的引擎有不一樣的GC實現方式。這裏就介紹V8 內存管理和垃圾回收機制

新生代和老生代

V8 將內存分爲兩類:新生代內存空間和老生代內存空間,新生代內存空間主要用來存放存活時間較短的對象,老生代內存空間主要用來存放存活時間較長的對象。對於垃圾回收,新生代和老生代有各自不一樣的策略。

 

new_old_generation.jpg

新生代主要使用Scavenge垃圾回收算法進行管理,主要實現是Cheney算法,將內存平均分爲兩塊,使用空間叫From,閒置空間叫To,新對象都先分配到From空間中,在空間快要佔滿時將存活對象複製到To空間中,而後清空From的內存空間,此時,調換From空間和To空間,繼續進行內存分配,當知足那兩個條件時對象會重新生代晉升到老生代。也就是上面提到的標記複製式的算法

老生代主要採用Mark-Sweep和Mark-Compact算法,一個是標記清除,一個是標記整理。二者不一樣的地方是,Mark-Sweep在垃圾回收後會產生碎片內存,而Mark-Compact在清除前會進行一步整理,將存活對象向一側移動,隨後清空邊界的另外一側內存,這樣空閒的內存都是連續的,可是帶來的問題就是速度會慢一些。在V8中,老生代是Mark-Sweep和Mark-Compact二者共同進行管理的。因爲Mark-Conpact須要移動對象,因此它的執行速度不可能很快,在取捨上,V8主要使用Mark-Sweep,在空間不足以對重新生代中晉升過來的對象進行分配時,才使用Mark-Compact。


3.javascript 內存泄露

3.1 全局變量引發的內存泄漏

3.2 閉包引發的內存泄漏

3.3 dom清空或刪除時,事件未清除致使的內存泄漏

3.4 子元素存在引用引發的內存泄漏

 

detached-nodes.gif

  • 黃色是指直接被 js變量所引用,在內存裏
  • 紅色是指間接被 js變量所引用,如上圖,refB 被 refA 間接引用,致使即便 refB 變量被清空,也是不會被回收的
  • 子元素 refB 因爲 parentNode 的間接引用,只要它不被刪除,它全部的父元素(圖中紅色部分)都不會被刪除

做者:echozzh
連接:https://www.jianshu.com/p/18532079bc2a 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索