摘要: JS是如何回收內存的?javascript
《JavaScript深刻淺出》系列:html
最近垃圾回收這個話題很是火,你們不能隨隨便便的扔垃圾了,還得先分類,這樣方便對垃圾進行回收再利用。java
其實,對於寫代碼來講,也有垃圾回收(garbage collection)這個問題,這裏所說的垃圾,指的是程序中再也不須要的內存空間,垃圾回收指的是回收這些再也不須要的內存空間,讓程序能夠從新利用這些釋放的內存空間。程序員
對於C這種底層語言來講,咱們可使用malloc()函數分配內存空間,當所分配的內存再也不須要的時候,可使用free()函數來釋放內存空間。算法
#include <stdio.h> #include <stdlib.h> #define TRUE 1 int main () { int *p, i, n, sum; while (TRUE) { printf ("請輸入數組長度: "); scanf ("%d", &n); p = (int *) malloc (n * sizeof (int)); // 分配內存空間 sum = 0; for (i = 0; i < n; ++i) { *(p + i) = i + 1; sum += *(p + i); } printf ("sum = %d\n", sum); free (p); // 釋放內存空間 } return 0; }
示例代碼很簡單,輸入一個整數n,程序計算一、二、3...n的和。你們能夠在Online C Compiler上運行這段代碼。編程
請輸入數組長度: 36 sum = 666 請輸入數組長度: 100 sum = 5050
若是咱們不去調用free()函數釋放內存的話,就會致使內存泄漏(memory leak)。每一個while循環中,指針p都會指向新分配的內存空間。而p以前指向的內存空間雖然沒用了,可是並不會被釋放,除非程序退出。若是while循環一直執行下去的話,內存遲早不夠用。小程序
若是讓咱們去手動管理內存,那不知道要寫出多少BUG,內存分分鐘用完。還好現代編程語言,好比Java, Python, Go以及JavaScript,都是支持自動垃圾回收的。也就是說,這些語言能夠自動回收程序再也不須要的內存空間,這樣既減輕了開發者的負擔,也有效避免了內存泄漏。微信小程序
其實,早在C語言誕生以前的1960年,圖靈獎得主John McCarthy就在Lisp語言中實現了自動垃圾回收算法。算法自己其實很是簡單,標記那些程序訪問不到的數據,回收它們的內存空間。可是,垃圾回收算法把程序員從硬件層(內存管理)解放出來了,這種理念仍是很先進的。數組
對於垃圾回收算法來講,最困難的問題是如何肯定哪些內存空間是能夠回收的,即哪些內存空間是程序再也不須要的,這是一個不可斷定問題(undecidable problem)。所謂不可斷定,就是沒有哪一個垃圾回收算法能夠肯定程序中全部能夠回收的內存空間。瀏覽器
McCarthy簡化了斷定數據是否須要的問題,將其簡化爲判斷數據是否可以訪問。若是程序已經不能訪問某個數據了,那這個數據天然是再也不須要了。可是,這個邏輯反過來是不成立的,一些能夠訪問的數據也有可能其實程序已經再也不須要了。
McCarthy的垃圾回收算法如今一般被稱做Mark-and-Sweep,它是如今不少語言(Java, JavaScript, Go)的垃圾回收算法的原型。
對於JavaScript來講,咱們是不須要手動管理內存的,由於JavaScript引擎例如V8與SpiderMonkey都會自動分配並回收內存。
比較古老的瀏覽器,好比IE6和IE7使用的垃圾回收算法是reference-counting:肯定對象是否被引用,沒有被引用的對象則能夠回收。這個算法沒法回收Circular Object,有可能會所以形成內存泄漏:
var div; window.onload = function() { div = document.getElementById('myDivElement'); div.circularReference = div; div.lotsOfData = new Array(10000).join('*'); };
div對象的circularReference屬性指向div自己,所以div對象始終「被引用」。若是使用reference-counting垃圾回收算法的話,則div對象永遠不會被回收。最新的瀏覽器很早就再也不使用reference-counting,所以Circular Object沒法回收的問題也就不存在了。
目前,主流的瀏覽器使用的垃圾回收算法都是基於mark-and-sweep:
算法思想並無超越McCarthy半個世紀以前的設計,只是在實現細節上作了大量的優化,V8的垃圾回收模塊Orinoco大體是這樣作的:
JS引擎的垃圾回收算法已經很是強大了,因此咱們做爲JavaScript開發者基本上感覺不到它的存在。
咱們經過Chrome開發者工具實際感覺一下垃圾回收算法的效果。
測試1:
var str = new Array(100000000).join("*"); setInterval(() => { console.log(str[0]); }, 1000);
str是一個超長字符串,所以會佔有很多的內存空間。代碼裏面寫了一個setInterval,是爲了讓這段代碼永遠執行下去,程序不退出。這樣的話,字符串str永遠在使用中,永遠是能夠訪問的,那它的內存空間就不會被回收。
我使用的是Chrome 75,在其開發者工具的Memory的Tab下,使用Take heap snapshot能夠獲取內存快照:
可知,內存佔用了97MB,且咱們能夠在其中找到str這個超長字符串。
測試2
var str = new Array(100000000).join("*"); setInterval(() => { console.log(str[0]); }, 1000); setTimeout(() => { str = "******"; }, 10000);
在setTimeout的回調函數中,咱們對str進行了從新賦值,這就意味着以前的超長字符串就不可訪問了,那它的內存空間就會被回收。
在代碼運行10s以後,即str從新賦值以後進行快照:
可知,內存只佔用了1.6MB,且咱們能夠在其中找到str字符串,它的長度只有6,所以佔用的內存空間很是小。
想象一下,若是再也不須要的內存空間不會被回收的話,1T的內存都不夠用。
關於JS,我打算花1年時間寫一個系列的博客JavaScript深刻淺出,你們還有啥不太清楚的地方?不妨留言一下,我能夠研究一下,而後再與你們分享一下。歡迎添加個人我的微信(KiwenLau),我是Fundebug的技術負責人,一個對JS又愛又恨的程序員。
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對一、微脈、青團社等衆多品牌企業。歡迎你們免費試用!
轉載時請註明做者 Fundebug以及本文地址:
https://blog.fundebug.com/2019/07/03/javascript-garbage-collection/