1. 程式人生 > 程式設計 >詳解JavaScript的垃圾回收機制

詳解JavaScript的垃圾回收機制

目錄
  • 為什麼需要垃圾回收(GC)
  • 什麼是垃圾回收
  • 垃圾產生
  • 垃圾回收策略
    • 引用計數標記
      • 迴圈引用引發的問題
      • 解決方法
      • 引用計數演算法的優缺點
    • 標記清除演算法
      • 核心思想
      • 標記清除演算法優缺點
      • 標記整理演算法
  • V8引擎的垃圾回收
    • 回收新生代物件
      • 物件晉升機制
    • 回收老生代物件
      • 參考文件:
      • 總結

        為什麼需要垃圾回收(GC)

        • 程式和人一樣,生活時間長了會產生垃圾,程式在執行過程中也會產生垃圾,垃圾積攢過多後,會導致程式執行速度變慢。
        • 在中的字串、物件、陣列等資料的記憶體是不固定的,只有真正使用的時候才會動態分配記憶體。
        • 這些資料所佔的記憶體在不使用時,需要進行釋放,以便再次使用,否者在可用記憶體耗盡造成程式崩潰。

        什麼是垃圾回收

        垃圾回收機制也稱Garbage Collection簡稱GC。在Script中擁有自動的垃圾回收機制,通過一些回收演算法,找出不再使用引用的變數或屬性,由引擎按照固定時間間隔週期性的釋放其所佔的記憶體空間。在C/C++中需要程式設計師手動完成垃圾回收。

        垃圾產生

        當一個物件沒有任何的變數或屬性對它進行引用,此時我們將永遠無法操作該物件,這種物件就是一個垃圾,這種物件過多會佔用大量的記憶體空間,導致程式變慢。

        例如:

        在這裡插入圖片描述

        這裡我先聲明瞭一個Person變數,它引用了物件{name: "江流",age: 20},接著我又將這個Person變數指向了另一個物件{name: "心猿",age: 5000},那麼之前被引用的物件,現在就成了無用物件,也永遠無法使用操作該物件,這種物件就是一個垃圾。

        這種垃圾物件過多,就會佔用大量空間,如果一直不釋放就會影響系統性能,重則導致程式崩潰,所以就需要垃圾回收釋放這部分記憶體。

        這個過程我們不需要也不能進行垃圾回收的操作。

        我們只需要的是將不再使用的物件設定為null即可。

        垃圾回收策略

        JavaScript 中主要的記憶體管理概念是可達性。大概意思是以某種方式可以訪問到或者可以使用的值,它們就是需要儲存在記憶體中,無法訪問,也無法使用的值,則需要被垃圾回收機制回收。

        垃圾回收過程是不實時進行的,因為JavaScript是一門單執行緒的語言,每次執行垃圾回收,會使程式應用邏輯暫停,執行完垃圾後回收在執行應用邏輯,這種行為稱為全停頓,所以一般垃圾回收會在cpu閒時進行。

        如何通過某種方式找到所謂的垃圾,是垃圾回收的重點,所以下面常見的演算法策略,不過這裡只說前兩種:

        1. 引用計數演算法
        2. 標記清除演算法
        3. 標記整理
        4. 分代回收

        引用計數標記

        策略思想:

        • 跟蹤記錄每個變數值被使用的次數
        • 當宣告一個變數並且將一個引用型別資料賦值給這個變數的時候,這個引用型別資料的引用次數就標記為 1
        • 如果當這個引用型別資料又賦值給另一個變數,那麼引用次數就+1
        • 如果變數被其他的值覆蓋,則引用次數-1
        • 當這個引用型別資料的引用次數變為0的時候,這個變數就沒有被使用了,也無法訪問,垃圾回收器就會在執行時,銷燬引用次數為0的引用型別資料,回收其所佔用的記憶體空間。

        例如:

        	let a = {
        	    name: "江流",age: 20
        		};    	//此時該物件的引用計數標記為1(a 引用)
        	let b = a;	//此時物件的引用計數標記為2(a、b 引用)
        	a = null;	//此時物件的引用計數標記為1((b 引用))
        	b = null;	//此時物件的引用計數標記為0(無變數引用)
        	... 		//等待GC 回收此物件
        

        但是這種方式有個很嚴重的問題 – 迴圈引用

        迴圈引用引發的問題

        在一個函式中,物件A的屬性指向物件B,物件B的屬性指向物件A,這個函式在執行完,物件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()執行完成後,在函式執行完,函式作用域中的資料物件A和物件B都應該被GC銷燬掉。

        如果執行多次,將會造成嚴重的記憶體洩漏。

        解決方法

        在函式結束時,將www.cppcns.com其指向null

        //切斷引用關係
        A = null;
        B = null;
        

        引用計數演算法的優缺點

        優點:

        • 引用計數為零時,發現垃圾立即回收
        • 最大限度減少程式暫停

        缺點:

        • 無法回收迴圈引用的物件
        • 空間開銷比較大

        標記清除演算法

        核心思想

        分標記和清除兩個階段完成。

        大概過程:

        • 垃圾收集器在執行時會給記憶體中所有的變數都加上一個標記,假設記憶體中所有的物件全部是垃圾,全部標記為0
        • 然後從各個根物件開始遍歷,把不是垃圾的節點改成1
        • 清理所有標記為0的垃圾,銷燬並回收它們所佔用的記憶體空間
        • 最後把所有記憶體中物件標記修改為0,等待下一輪的垃圾回收

        在這裡插入圖片描述

        標記清除演算法優缺點

        優點:

        • 實現簡單,標記情況無非是打與不打的兩種情況,通過二進位制(0和1)就可以為其標記。
        • 能夠回收迴圈引用的物件
        • 是v8引擎使用最多的演算法。

        缺點:

        在清除垃圾之後,剩餘物件的記憶體位置是不變的,就會導致空閒記憶體空間不連續。這樣就出現了記憶體碎片,並且由於剩餘空間不是整塊,就需要記憶體分配的問題。

        標記整理演算法

        標記整理(Mark-Compact)演算法,就是可以有效的解決,它是在標記結束後標記整理演算法會將不需要清理的物件向記憶體一端移動,最後清理邊界的記憶體。

        在這裡插入圖片描述

        V8引擎的垃圾回收

        • V8引擎的垃圾回收採用標記清除法與分代回收法
        • 分為新生代和老生代

        針對不同物件採用不同演算法:

        (1)新生代:物件的存活時間較短。新生物件或只經過一次垃圾回收的物件。

        (2)老生代:物件存活時間較長。經歷過一次或多次垃圾回收的物件。

        回收新生代物件

        回收新生代物件主要採用複製演算法(Scavenge 演算法)加標記整理演算法。而Scavenge 演算法的具體實現,主要採用了Cheney演算法。

        物件晉升機制

        一輪GC還存活的新生代需要晉升。

        回收老生代物件

        回收老生代物件主要採用標記清除、標記整理、增量標記演算法,主要使dXsUFlauviwww.cppcns.com標記清除演算法,只有在記憶體分配不足時,採用標記整理演算法。

        • 首先使用標記清除完成垃圾空間的回收;
        • 採用標記整理進行空間優化;
        • 採用增量標記進行效率優化;

        參考文件:

        JS垃圾回收機制

        JavaScript GC 垃圾回收機制

        http://www.cppcns.com

        總結

        本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注我們的更多內容!