對比js與v8垃圾回收機制

前面的話

js具有自動回收機制,它會定期的找出那些不在繼續使用的變量,然後釋放其內存;而c、c++語言必須手動釋放內存,程序員負責內存管理。

回收方法

  • 標記清除

    標記清除是最常用的垃圾回收方式。 當變量進入執行環境時,就標記這個變量爲"進入環境"。 當變量離開環境時,則將其標記爲"離開環境"。被標記爲離開環境的變量,在下一次垃圾回收啓動時,就會被釋放掉佔用的空間。

    舉個例子:

     var m = 0 ,n = 4;// 把 m, n,add() 標記爲進入環境
     add(m ,n);// 把 a,b,c標記進入環境。
     console.log(n);// add() 函數執行完之後,將a,b,c標記爲離開環境,等待垃圾回收
     function add(a, b) {
    	 a++;
    	 var c = a + b;
    	 return c;
     }
    
  • 引用計數

    跟蹤記錄每個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數減1。當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其佔用的內存空間回收回來。這樣,當垃圾回收器下次再運行時,它就會釋放那些引用次數爲0的值所佔用的內存。

    缺點:無法解決循環引用:

    function func() {
        let obj1 = {};
        let obj2 = {};
    
        obj1.a = obj2; // obj1 引用 obj2
        obj2.a = obj1; // obj2 引用 obj1
    }
    

    當函數func執行結束之後,返回值undefined,所以整個函數以及內部的變量都應該被回收,但是,引用計數方法,obj1和obj2的引用次數都不爲0,所以他們不會被回收。

    要解決循環引用的問題,最好是在不使用它們的時候將它們手動設爲空。上面的例子可以這樣:

     obj1 = null;
     obj2 = null;
    

哪些會引起內存泄漏

內存泄漏:不再用到的內存,沒有及時釋放,就叫做內存泄漏(memory leak)。

  • 意外的全局變量:比如在函數內部,沒用使用var聲明的變量。
  • 被遺忘的計時器:計時器沒有被清除
  • 閉包
  • 沒有被清理的DOM元素引用

內存泄漏的識別方法

在新版本的Chrome中的performance中查看
在這裏插入圖片描述
圖中Heap對應的部分就是內存在週期性的回落。

如何避免泄漏

  • 減少不必要的全局變量,或者生命週期較長的對象,及時對無用的數據進行垃圾回收
  • 避免死循環
  • 避免創建過多的對象

總之:不用了的東西要及時歸還。

V8引擎回收機制

內存申請:

  • 64位系統:1.4GB
  • 32位系統:700MB

在node環境中可以通過:process.memoryUsage()來查看內存分配。
在這裏插入圖片描述

  • rss: 所有內存佔用
  • heapTotal:v8引擎可以分配的最大堆內存
  • heapUsed:v8引擎分配使用的堆內存
  • external:v8管理c++對象綁定到JavaScript對象的內存

以上單位都是字節。

Node的整體架構

node

  • 第一層: Node標準庫,由js編寫,是我們使用過程中直接能調用的API。在源碼中的lib目錄下。比如常用的http模塊

  • 第二層:Node bindings,這一層是js與底層c/c++能夠溝通的關鍵,前者通過bindings調用後者,相互交換數據。

  • 第三層:是支撐Node運行的關鍵,由c/c++實現。

      1、v8是Google開發的JavaScript引擎,提供JavaScript運行環境,可以說
      	它就是Node.js的發動機。
      2、libuv是專門爲Node.js開發的一個封裝庫,提供跨平臺,線程池、
        事件池、異步I/O能力,是Node.js如此強大的關鍵。
      3、C-ares提供了異步處理DNS相關的能力
      4、http_parser、 OpenSSL、zlib等提供http解析,ssl、數據壓縮等其他能力
    
v8垃圾回收策略

我們不可能只用一種回收策略來解決問題,這樣效率會很低。所以,v8採用了分代回收的策略,將內存分爲兩個帶:新生代老生代

  • 新生代中的對象:爲存活時間較短的對象
  • 老生代中的對象:爲存活較長或常駐內存的對象
分代內存:
  • 32位中,新生代內存大小16MB,老生代內存大小700MB
  • 64位中,新生代內存大小32MB,老生代內存大小1.4GB
  • 新生代平均分爲兩塊相等的內存空間,叫做semispace,每塊內存大小8MB(32位)或者16MB(64位)

在這裏插入圖片描述

新生代

新生代採用Scavenge垃圾回收算法,在算法實現時主要採用Cheney算法。

Cheney算法將新生代的內存分爲兩塊semispace,一塊處於使用狀態,稱爲From空間,一塊使用閒置狀態,稱爲To空間

繪圖說明:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
上圖中,一直將From和To交換,就是爲了讓活躍對象始終在From中,To始終保持閒置。

這種算法的缺點是:只能使用堆內存的一半。

由於Scavenge是典型的犧牲空間換取時間的算法,所以無法大規模的應用到所有的垃圾回收中。但我們可以看到,Scavenge非常適合應用在新生代中,因爲新生代中對象的生命週期較短,恰恰適合這個算法。

當對象經過多次複製任然存活,它就會被認爲是生命週期較長的對象。這種較長生命週期的對象隨後會被移動到老生代中。採用新的算法進行管理。

對象從新生代移到老生代的過程叫作晉升。

晉升的條件:

  • 如果一個對象是第二次經歷從From空間複製到To空間,那麼這個對象會被移動到老生代中。
  • 當要從From空間複製一個對象到To空間時,如果To空間已經使用了超過25%,則這個對象直接晉升到老生代中。設置25%這個閾值的原因是當這次Scavenge回收完成後,這個To空間會變爲From空間,接下來的內存分配將在這個空間中進行。如果佔比過高,會影響後續的內存分配。
老生代

由於老生代中的對象存活時間長,使用上面的方法將不在適用。老生代將使用Mark-SweepMark-Compact相結合的方式進行垃圾回收。

1、Mark-Sweep:

Mark-Sweep是標記清除的意思,它分爲標記和清除兩個階段。Mark-Sweep在標記階段遍歷堆內存中的所有對象,並標記活着的對象,在隨後的清除階段,只清除沒有被標記的對象。

注意: js中的標記是標記所有的變量,清除掉被標記爲離開狀態的變量;而老生代中的標記使標記存活的變量,清除沒有被標記的變量。
在這裏插入圖片描述
在這裏插入圖片描述
缺點: 從圖中可以看出,在進行一次清除回收之後,內存空間會出現不連續的狀態。這種內存碎片會對後續的內存分配造成問題。如果出現需要分配一個大內存的情況,由於剩餘的碎片空間不足以完成此次分配,就會提前觸發垃圾回收,而這次回收是不必要的。

2、Mark-Compact

爲了解決Mark-Sweep的內存碎片問題,Mark-Compact就被提出來了。

Mark-Compact是標記整理的意思,會將標記存活對象以後,將活着的對象向內存空間的一端移動,移動完成後,直接清理掉邊界外的內存。

在這裏插入圖片描述
在這裏插入圖片描述
3、兩者結合

在V8的回收策略中,Mark-Sweep和Mark-Conpact兩者是結合使用的。由於Mark-Conpact需要移動對象,所以它的執行速度不可能很快,在取捨上,V8主要使用Mark-Sweep,在空間不足以對從新生代中晉升過來的對象進行分配時,才使用Mark-Compact。

參考文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章