溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript深入淺出第3課:什么是垃圾回收算法?

發(fā)布時間:2020-07-08 12:33:22 來源:網(wǎng)絡(luò) 閱讀:264 作者:Fundebug 欄目:web開發(fā)

摘要: JS是如何回收內(nèi)存的?

《JavaScript深入淺出》系列

  • JavaScript深入淺出第1課:箭頭函數(shù)中的this究竟是什么鬼?
  • JavaScript深入淺出第2課:函數(shù)是一等公民是什么意思呢?
  • JavaScript深入淺出第3課:什么是垃圾回收算法?

最近垃圾回收這個話題非?;穑蠹也荒茈S隨便便的扔垃圾了,還得先分類,這樣方便對垃圾進行回收再利用。

其實,對于寫代碼來說,也有垃圾回收(garbage collection)這個問題,這里所說的垃圾,指的是程序中不再需要的內(nèi)存空間,垃圾回收指的是回收這些不再需要的內(nèi)存空間,讓程序可以重新利用這些釋放的內(nèi)存空間。

手動管理內(nèi)存

對于C這種底層語言來說,我們可以使用malloc()函數(shù)分配內(nèi)存空間,當(dāng)所分配的內(nèi)存不再需要的時候,可以使用free()函數(shù)來釋放內(nèi)存空間。

#include <stdio.h>
#include <stdlib.h>
#define TRUE 1

int main ()
{
  int *p, i, n, sum;

  while (TRUE)
  {
      printf ("請輸入數(shù)組長度: ");
      scanf ("%d", &n);
      p = (int *) malloc (n * sizeof (int)); // 分配內(nèi)存空間
      sum = 0;
      for (i = 0; i < n; ++i)
      {
        *(p + i) = i + 1;
        sum += *(p + i);
      }
      printf ("sum = %d\n", sum);
      free (p); // 釋放內(nèi)存空間
  }
  return 0;
}

示例代碼很簡單,輸入一個整數(shù)n,程序計算1、2、3...n的和。大家可以在Online C Compiler上運行這段代碼。

請輸入數(shù)組長度: 36                                                                                                                                                                                                           
sum = 666                                                                                                                                                                                                                    
請輸入數(shù)組長度: 100                                                                                                                                                                                                          
sum = 5050 

如果我們不去調(diào)用free()函數(shù)釋放內(nèi)存的話,就會導(dǎo)致內(nèi)存泄漏(memory leak)。每個while循環(huán)中,指針p都會指向新分配的內(nèi)存空間。而p之前指向的內(nèi)存空間雖然沒用了,但是并不會被釋放,除非程序退出。如果while循環(huán)一直執(zhí)行下去的話,內(nèi)存早晚不夠用。

垃圾回收算法

如果讓我們?nèi)ナ謩庸芾韮?nèi)存,那不知道要寫出多少BUG,內(nèi)存分分鐘用完。還好現(xiàn)代編程語言,比如Java, Python, Go以及JavaScript,都是支持自動垃圾回收的。也就是說,這些語言可以自動回收程序不再需要的內(nèi)存空間,這樣既減輕了開發(fā)者的負擔(dān),也有效避免了內(nèi)存泄漏。

其實,早在C語言誕生之前的1960年,圖靈獎得主John McCarthy就在Lisp語言中實現(xiàn)了自動垃圾回收算法。算法本身其實非常簡單,標記那些程序訪問不到的數(shù)據(jù),回收它們的內(nèi)存空間。但是,垃圾回收算法把程序員從硬件層(內(nèi)存管理)解放出來了,這種理念還是很先進的。

對于垃圾回收算法來說,最困難的問題是如何確定哪些內(nèi)存空間是可以回收的,即哪些內(nèi)存空間是程序不再需要的,這是一個不可判定問題(undecidable problem)。所謂不可判定,就是沒有哪個垃圾回收算法可以確定程序中所有可以回收的內(nèi)存空間。

McCarthy簡化了判定數(shù)據(jù)是否需要的問題,將其簡化為判斷數(shù)據(jù)是否能夠訪問。如果程序已經(jīng)不能訪問某個數(shù)據(jù)了,那這個數(shù)據(jù)自然是不再需要了。但是,這個邏輯反過來是不成立的,一些可以訪問的數(shù)據(jù)也有可能其實程序已經(jīng)不再需要了。

McCarthy的垃圾回收算法現(xiàn)在通常被稱作Mark-and-Sweep,它是現(xiàn)在很多語言(Java, JavaScript, Go)的垃圾回收算法的原型。

JavaScript的垃圾回收算法

對于JavaScript來說,我們是不需要手動管理內(nèi)存的,因為JavaScript引擎例如V8與SpiderMonkey都會自動分配并回收內(nèi)存。

比較古老的瀏覽器,比如IE6和IE7使用的垃圾回收算法是reference-counting:確定對象是否被引用,沒有被引用的對象則可以回收。這個算法無法回收Circular Object,有可能會因此造成內(nèi)存泄漏:

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

  • root對象包括全局對象以及程序當(dāng)前的執(zhí)行堆棧;
  • 從root對象,遍歷其所有子對象,能夠通過遍歷訪問到的對象是可以訪問的;
  • 其他不能遍歷對象是不可訪問的,其內(nèi)存空間可以回收;

算法思想并沒有超越McCarthy半個世紀之前的設(shè)計,只是在實現(xiàn)細節(jié)上做了大量的優(yōu)化,V8的垃圾回收模塊Orinoco大致是這樣做的:

  • 采用多線程的方式進行垃圾回收,盡量避免對JavaScript本身的代碼執(zhí)行造成暫停;
  • 利用瀏覽器渲染頁面的空閑時間進行垃圾回收;
  • 根據(jù)The Generational Hypothesis,大多數(shù)對象的生命周期非常短暫,因此可以將對象根據(jù)生命周期進行區(qū)分,生命周期短的對象與生命周期長的對象采用不同的方式進行垃圾回收;
  • 對內(nèi)存空間進行整理,消除內(nèi)存碎片化,最大化利用釋放的內(nèi)存空間;

JS引擎的垃圾回收算法已經(jīng)非常強大了,所以我們作為JavaScript開發(fā)者基本上感受不到它的存在。

觀察JavaScript垃圾回收算法

我們通過Chrome開發(fā)者工具實際感受一下垃圾回收算法的效果。

測試1:

var str = new Array(100000000).join("*");

setInterval(() => {
    console.log(str[0]);
}, 1000);

str是一個超長字符串,因此會占有不少的內(nèi)存空間。代碼里面寫了一個setInterval,是為了讓這段代碼永遠執(zhí)行下去,程序不退出。這樣的話,字符串str永遠在使用中,永遠是可以訪問的,那它的內(nèi)存空間就不會被回收。

我使用的是Chrome 75,在其開發(fā)者工具的Memory的Tab下,使用Take heap snapshot可以獲取內(nèi)存快照:

JavaScript深入淺出第3課:什么是垃圾回收算法?

可知,內(nèi)存占用了97MB,且我們可以在其中找到str這個超長字符串。

測試2

var str = new Array(100000000).join("*");

setInterval(() => {
    console.log(str[0]);
}, 1000);

setTimeout(() => {
    str = "******";
}, 10000);

在setTimeout的回調(diào)函數(shù)中,我們對str進行了重新賦值,這就意味著之前的超長字符串就不可訪問了,那它的內(nèi)存空間就會被回收。

在代碼運行10s之后,即str重新賦值之后進行快照:

JavaScript深入淺出第3課:什么是垃圾回收算法?

可知,內(nèi)存只占用了1.6MB,且我們可以在其中找到str字符串,它的長度只有6,因此占用的內(nèi)存空間非常小。

想象一下,如果不再需要的內(nèi)存空間不會被回收的話,1T的內(nèi)存都不夠用。

關(guān)于JS,我打算花1年時間寫一個系列的博客JavaScript深入淺出,大家還有啥不太清楚的地方?不妨留言一下,我可以研究一下,然后再與大家分享一下。歡迎添加我的個人微信(KiwenLau),我是Fundebug的技術(shù)負責(zé)人,一個對JS又愛又恨的程序員。

參考

  • MDN:Memory Management
  • 為什么Lisp語言如此先進?
  • Recursive Functions of Symbolic Expressions Their Computation by Machine(Part I)
  • Trash talk: the Orinoco garbage collector
  • Idle-Time Garbage-Collection Scheduling

關(guān)于Fundebug

Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應(yīng)用實時BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃編程、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業(yè)。歡迎大家免費試用!

JavaScript深入淺出第3課:什么是垃圾回收算法?

版權(quán)聲明

轉(zhuǎn)載時請注明作者 Fundebug以及本文地址:
https://blog.fundebug.com/2019/07/03/javascript-garbage-collection/

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI