天天看點

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

摘要: JS是如何回收記憶體的?

JavaScript深入淺出 》系列:

最近垃圾回收這個話題非常火,大家不能随随便便的扔垃圾了,還得先分類,這樣友善對垃圾進行回收再利用。

其實,對于寫代碼來說,也有垃圾回收(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,程式計算1、2、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來說,我們是不需要手動管理記憶體的,因為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:

  • root對象包括全局對象以及程式目前的執行堆棧;
  • 從root對象,周遊其所有子對象,能夠通過周遊通路到的對象是可以通路的;
  • 其他不能周遊對象是不可通路的,其記憶體空間可以回收;

算法思想并沒有超越McCarthy半個世紀之前的設計,隻是在實作細節上做了大量的優化,V8的垃圾回收子產品Orinoco

大緻是這樣做的

  • 采用多線程的方式進行垃圾回收,盡量避免對JavaScript本身的代碼執行造成暫停;
  • 利用浏覽器渲染頁面的空閑時間進行垃圾回收;
  • 根據 The Generational Hypothesis ,大多數對象的生命周期非常短暫,是以可以将對象根據生命周期進行區分,生命周期短的對象與生命周期長的對象采用不同的方式進行垃圾回收;
  • 對記憶體空間進行整理,消除記憶體碎片化,最大化利用釋放的記憶體空間;

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

觀察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可以擷取記憶體快照:

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

可知,記憶體占用了97MB,且我們可以在其中找到str這個超長字元串。

測試2

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

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

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

在setTimeout的回調函數中,我們對str進行了重新指派,這就意味着之前的超長字元串就不可通路了,那它的記憶體空間就會被回收。

在代碼運作10s之後,即str重新指派之後進行快照:

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

可知,記憶體隻占用了1.6MB,且我們可以在其中找到str字元串,它的長度隻有6,是以占用的記憶體空間非常小。

想象一下,如果不再需要的記憶體空間不會被回收的話,1T的記憶體都不夠用。

關于JS,我打算花1年時間寫一個系列的部落格

,大家還有啥不太清楚的地方?不妨留言一下,我可以研究一下,然後再與大家分享一下。歡迎添加我的個人微信(KiwenLau),我是

Fundebug

的技術負責人,一個對JS又愛又恨的程式員。

參考

關于Fundebug

專注于JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃程式設計、荔枝FM、掌門1對1、微脈、青團社等衆多品牌企業。歡迎大家

免費試用

版權聲明

轉載時請注明作者 Fundebug以及本文位址:

https://blog.fundebug.com/2019/07/03/javascript-garbage-collection/

繼續閱讀