詳解JavaScript的垃圾回收機制
為什么需要垃圾回收(GC)
- 程序和人一樣,生活時間長了會產生垃圾,程序在運行過程中也會產生垃圾,垃圾積攢過多后,會導致程序運行速度變慢。
- 在JavaScript中的字符串、對象、數組等數據的內存是不固定的,只有真正使用的時候才會動態(tài)分配內存。
- 這些數據所占的內存在不使用時,需要進行釋放,以便再次使用,否者在可用內存耗盡造成程序崩潰。
什么是垃圾回收
垃圾回收機制也稱Garbage Collection簡稱GC。在JavaScript中擁有自動的垃圾回收機制,通過一些回收算法,找出不再使用引用的變量或屬性,由JS引擎按照固定時間間隔周期性的釋放其所占的內存空間。在C/C++中需要程序員手動完成垃圾回收。
垃圾產生
當一個對象沒有任何的變量或屬性對它進行引用,此時我們將永遠無法操作該對象,這種對象就是一個垃圾,這種對象過多會占用大量的內存空間,導致程序變慢。
例如:

這里我先聲明了一個Person變量,它引用了對象{name: "江流",age: 20},接著我又將這個Person變量指向了另一個對象{name: "心猿", age: 5000},那么之前被引用的對象,現在就成了無用對象,也永遠無法使用操作該對象,這種對象就是一個垃圾。
這種垃圾對象過多,就會占用大量空間,如果一直不釋放就會影響系統(tǒng)性能,重則導致程序崩潰,所以就需要垃圾回收釋放這部分內存。
這個過程我們不需要也不能進行垃圾回收的操作。
我們只需要的是將不再使用的對象設置為null即可。
垃圾回收策略
JavaScript 中主要的內存管理概念是可達性。大概意思是以某種方式可以訪問到或者可以使用的值,它們就是需要保存在內存中,無法訪問,也無法使用的值,則需要被垃圾回收機制回收。
垃圾回收過程是不實時進行的,因為JavaScript是一門單線程的語言,每次執(zhí)行垃圾回收,會使程序應用邏輯暫停,執(zhí)行完垃圾后回收在執(zhí)行應用邏輯,這種行為稱為全停頓,所以一般垃圾回收會在cpu閑時進行。
如何通過某種方式找到所謂的垃圾,是垃圾回收的重點,所以下面常見的算法策略,不過這里只說前兩種:
- 引用計數算法
- 標記清除算法
- 標記整理
- 分代回收
引用計數標記
策略思想:
- 跟蹤記錄每個變量值被使用的次數
- 當聲明一個變量并且將一個引用類型數據賦值給這個變量的時候,這個引用類型數據的引用次數就標記為 1
- 如果當這個引用類型數據又賦值給另一個變量,那么引用次數就+1
- 如果變量被其他的值覆蓋,則引用次數-1
- 當這個引用類型數據的引用次數變?yōu)?的時候,這個變量就沒有被使用了,也無法訪問,垃圾回收器就會在執(zhí)行時,銷毀引用次數為0的引用類型數據,回收其所占用的內存空間。
例如:
let a = {
name: "江流",
age: 20
}; //此時該對象的引用計數標記為1(a 引用)
let b = a; //此時對象的引用計數標記為2(a、b 引用)
a = null; //此時對象的引用計數標記為1((b 引用))
b = null; //此時對象的引用計數標記為0(無變量引用)
... //等待GC 回收此對象
但是這種方式有個很嚴重的問題 – 循環(huán)引用
循環(huán)引用引發(fā)的問題
在一個函數中,對象A的屬性指向對象B,對象B的屬性指向對象A,這個函數在執(zhí)行完,對象A和B的計數器也不會為0,影響了正常的GC。
例如下面的例子:
function test()
{
let A = new Object();
let B = new Object();
A.pointer= B;
B.pointer = A;
}
test();
當對象A和對象B的屬性相互引用這,按照引用計數策略,他們的引用計數都是為2,但是在test()執(zhí)行完成后,在函數執(zhí)行完,函數作用域中的數據對象A和對象B都應該被GC銷毀掉。
如果執(zhí)行多次,將會造成嚴重的內存泄漏。
解決方法
在函數結束時,將其指向null
//切斷引用關系 A = null; B = null;
引用計數算法的優(yōu)缺點
優(yōu)點:
- 引用計數為零時,發(fā)現垃圾立即回收
- 最大限度減少程序暫停
缺點:
- 無法回收循環(huán)引用的對象
- 空間開銷比較大
標記清除算法
核心思想
分標記和清除兩個階段完成。
大概過程:
- 垃圾收集器在運行時會給內存中所有的變量都加上一個標記,假設內存中所有的對象全部是垃圾,全部標記為0
- 然后從各個根對象開始遍歷,把不是垃圾的節(jié)點改成1
- 清理所有標記為0的垃圾,銷毀并回收它們所占用的內存空間
- 最后把所有內存中對象標記修改為0,等待下一輪的垃圾回收

標記清除算法優(yōu)缺點
優(yōu)點:
- 實現簡單,標記情況無非是打與不打的兩種情況,通過二進制(0和1)就可以為其標記。
- 能夠回收循環(huán)引用的對象
- 是v8引擎使用最多的算法。
缺點:
在清除垃圾之后,剩余對象的內存位置是不變的,就會導致空閑內存空間不連續(xù)。這樣就出現了內存碎片,并且由于剩余空間不是整塊,就需要內存分配的問題。
標記整理算法
標記整理(Mark-Compact)算法,就是可以有效的解決,它是在標記結束后標記整理算法會將不需要清理的對象向內存一端移動,最后清理邊界的內存。

V8引擎的垃圾回收
- V8引擎的垃圾回收采用標記清除法與分代回收法
- 分為新生代和老生代
針對不同對象采用不同算法:
(1)新生代:對象的存活時間較短。新生對象或只經過一次垃圾回收的對象。
(2)老生代:對象存活時間較長。經歷過一次或多次垃圾回收的對象。
回收新生代對象
回收新生代對象主要采用復制算法(Scavenge 算法)加標記整理算法。而Scavenge 算法的具體實現,主要采用了Cheney算法。
對象晉升機制
一輪GC還存活的新生代需要晉升。
回收老生代對象
回收老生代對象主要采用標記清除、標記整理、增量標記算法,主要使用標記清除算法,只有在內存分配不足時,采用標記整理算法。
- 首先使用標記清除完成垃圾空間的回收;
- 采用標記整理進行空間優(yōu)化;
- 采用增量標記進行效率優(yōu)化;
參考文檔:
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
相關文章
Extjs gridpanel 中的checkbox(復選框)根據某行的條件不能選中的解決方法
這篇文章主要介紹了Extjs gridpanel 中的checkbox(復選框)根據某行的條件不能選中的解決方法,需要的朋友可以參考下2017-02-02
JavaScript調試技巧之console.log()詳解
對于JavaScript程序的調試,相比于alert(),使用console.log()是一種更好的方式,原因在于:alert()函數會阻斷JavaScript程序的執(zhí)行,從而造成副作用;而console.log()僅在控制臺中打印相關信息,因此不會造成類似的顧慮2014-03-03

