首頁  >  文章  >  web前端  >  javascript有gc嗎

javascript有gc嗎

青灯夜游
青灯夜游原創
2021-10-09 16:55:332813瀏覽

javascript中有GC(垃圾回收機制)。 JavaScript是使用垃圾回收機制的語言,執行環境負責在程式碼執行時管理內存,會自動將垃圾物件(沒有被引用的物件)從記憶體中銷毀。

本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。

JavaScript 中的垃圾回收機制(GC)

#垃圾回收相關概念

①什麼是垃圾

沒有被使用(引用)的物件就是垃圾

② 什麼是垃圾回收

沒有被引用的物件被銷毀,記憶體被釋放,就是垃圾回收

C、C 等程式語言需要手動垃圾回收。

Java、JavaScript、PHP、Python 等語言自動垃圾回收。

JS中擁有自動的垃圾回收機制,會自動將這些垃圾物件從記憶體中銷毀,我們不需要也不能進行垃圾回收的操作。我們需要做的只是要將不再使用的物件設為 null 即可。

為什麼需要垃圾回收

  • 在C / C 中,追蹤記憶體的使用和管理記憶體對開發者來說是很大的負擔
    • JavaScript是使用垃圾回收機制的語言,也就是說執行環境負責在程式碼執行時管理內存,幫開發者卸下了這個負擔
    • #透過自動記憶體管理實現記憶體的分配和資源的回收
    • 基本想法很簡單,確定哪個變數不會再被使用了,把它的記憶體空間釋放
    • 這個過程是週期性的,意思是這個垃圾回收程式每隔一段時間就會運行一次
  • 像JS中的物件、字串、物件的記憶體是不固定的,只有真正用到的時候才會動態分配記憶體
    • 這些記憶體需在不使用後進行釋放以便再次使用,否則在電腦可用記憶體耗儘後造成當機
  • 瀏覽器發展史上的垃圾回收法主要有
    • 引用計數法
    • 標記清除法

引用計數法

想法

  • 變數只是對值進行引用
  • 當變數引用該值時,引用次數1
  • 當變數的參考被覆寫或清除時,引用次數-1
  • 當引用次數為0時,就可以安全地釋放這塊記憶體。
let arr = [1, 0, 1]   // [1, 0, 1]这块内存被arr引用  引用次数为1
arr = [0, 1, 0]  // [1, 0, 1]的内存引用次数为0被释放  
                 // [0, 1, 0]的内存被arr引用   引用次数为1
const tmp = arr  // [0, 1, 0]的内存被tmp引用   引用次数为2

循環參考問題

#Netscape Navigator 3.0 採用

  • 在這個例子中,ObjectA和ObjectB的屬性分別互相引用
  • 造成這個函數執行後,Object被引用的次數不會變成0,影響了正常的GC。
  • 如果執行多次,將造成嚴重的記憶體洩漏。
  • 而標記清除法則不會有這個問題。
function Example(){

    let ObjectA = new Object();
    let ObjectB = new Object();

    ObjectA.p = ObjectB;
    ObjectB.p = ObjectA;   

}

Example();
  • 解決方法:在函數結束時將其指向null
#
ObjectA = null;
ObjectB = null;

標記清除法

為了解決循環引用造成的記憶體洩漏問題,Netscape Navigator 4.0 開始採用標記清除法

到了2008 年,IE、Firefox、Opera、Chrome 和Safari 都在自己的JavaScript 實作中採用標記清理(或其變體),只是在運行垃圾回收的頻率上有所差異。

思路

  • 在變數進入執行上下文時打上「進入」標記
  • 同時在變數離開執行上下文時也打上「離開」標記
    • 從此以後,無法存取這個變數
    • 在下一次垃圾回收時進行記憶體的釋放
function Example(n){
    const a = 1, b = 2, c = 3;
    return n * a * b * c;
}
// 标记Example进入执行上下文

const n = 1;  // 标记n进入执行上下文
Example(n);   // 标记a,b,c进入执行上下文
console.log(n); // 标记a, b, c离开执行上下文,等待垃圾回收

#const和let宣告提升效能

  • const和let不僅有助於改善程式碼風格,同時有利於垃圾回收效能的提升
  • const和let使JS有了區塊級作用域,當區塊級作用域比函數作用域更早結束時,垃圾回收程式更早介入
  • 儘早回收該回收的內存,提升了垃圾回收的性能

V8引擎的垃圾回收

V8引擎的垃圾回收採用標記清除法與分代回收法

分為新生代和老生代

新生代

新生代垃圾回收採用Scavenge 演算法

分配給常用記憶體和新分配的小量記憶體

  • 記憶體大小

    • 32位元系統16M記憶體
    • 64位元系統32M記憶體
  • #分割區

      ##新代記憶體分為以下兩區,記憶體各佔一半
    • From space
    • To space
  • #運行

      實際運行的只有From space
    • To space處於空閒狀態
  • #Scavenge演算法

    • 当From space内存使用将要达到上限时开始垃圾回收,将From space中的不可达对象都打上标记
    • 将From space的未标记对象复制到To space。
      • 解决了内存散落分块的问题(不连续的内存空间)
      • 相当于用空间换时间。
    • 然后清空From space、将其闲置,也就是转变为To space,俗称反转。
  • 新生代 -> 老生代

    • 新生代存放的是新分配的小量内存,如果达到以下条件中的一个,将被分配至老生代
      • 内存大小达到From space的25%
      • 经历了From space To space的一个轮回

老生代

老生代采用mark-sweep标记清除和mark-compact标记整理

通常存放较大的内存块和从新生代分配过来的内存块

  • 内存大小
    • 32位系统700M左右
    • 64位系统1.4G左右
  • 分区
    • Old Object Space
      • 字面的老生代,存放的是新生代分配过来的内存。
    • Large Object Space
      • 存放其他区域放不下的较大的内存,基本都超过1M
    • Map Space
      • 存放存储对象的映射关系
    • Code Space
      • 存储编译后的代码
  • 回收流程
    • 标记分类(三色标记)
      • 未被扫描,可回收,下面简称1类
      • 扫描中,不可回收,下面简称2类
      • 扫描完成,不可回收,下面简称3类
    • 遍历
      • 采用深度优先遍历,遍历每个对象。
      • 首先将非根部对象全部标记为1类,然后进行深度优先遍历。
      • 遍历过程中将对象压入栈,这个过程中对象被标记为2类
      • 遍历完成对象出栈,这个对象被标记为3类
      • 整个过程直至栈空
    • Mark-sweep
      • 标记完成之后,将标记为1类的对象进行内存释放

  • Mark-compact

    • 垃圾回收完成之后,内存空间是不连续的。

    • 这样容易造成无法分配较大的内存空间的问题,从而触发垃圾回收。

    • 所以,会有Mark-compact步骤将未被回收的内存块整理为连续地内存空间。

    • 频繁触发垃圾回收会影响引擎的性能,内存空间不足时也会优先触发Mark-compact

垃圾回收优化

  • 增量标记
    • 如果用集中的一段时间进行垃圾回收,新生代倒还好,老生代如果遍历较大的对象,可能会造成卡顿。
    • 增量标记:使垃圾回收程序和应用逻辑程序交替运行,思想类似Time Slicing
  • 并行回收
    • 在垃圾回收的过程中,开启若干辅助线程,提高垃圾回收效率。
  • 并发回收
    • 在逻辑程序执行的过程中,开启若干辅助线程进行垃圾回收,清理和主线程没有任何逻辑关系的内存。

内存泄露场景

全局变量

// exm1
function Example(){
    exm = 'LeBron'   
}

// exm2
function Example(){
    this.exm = 'LeBron'
}
Example()

未清除的定时器

const timer = setInterval(() => {
    //...
}, 1000)

// clearInterval(timer)

闭包

function debounce(fn, time) {
  let timeout = null; 
  return function () {
    if (timeout) {
      clearTimeout(timeout);
    }

    timeout = setTimeout(() => {
      fn.apply(this, arguments);
    }, time);
  };
}

const fn = debounce(handler, 1000); // fn引用了timeout

未清除的DOM元素引用

const element = {
    // 此处引用了DOM元素
    button:document.getElementById('LeBron'),
    select:document.getElementById('select')
}

document.body.removeChild(document.getElementById('LeBron'))

如何检测内存泄漏

这个其实不难,浏览器原带的开发者工具Performance就可以

  • 步骤
    • F12打开开发者工具
    • 选择Performance工具栏
    • 勾选屏幕截图和Memory
    • 点击开始录制
    • 一段时间之后结束录制
  • 结果
    • 堆内存会周期性地分配和释放
    • 如果堆内存的min值在逐渐上升则存在内存泄漏

优化内存使用

1、尽量不在for循环中定义函数

// exm
const fn = (idx) => {
    return idx * 2;
}

function Example(){
    for(let i=0;i<1000;i++){
        //const fn = (idx) => {
        //    return idx * 2;
        // }
        const res = fn(i);
    }
}

2、尽量不在for循环中定义对象

function Example() {
  const obj = {};
  let res = "";
  for (let i = 0; i < 1000; i++) {
    // const obj = {
    //   a: i,
    //   b: i * 2,
    //   c: i * 3,
    // };
    obj.a = i;
    obj.b = i * 2;
    obj.c = i * 3;
    res += JSON.stringify(obj);
  }
  return res
}

3、清空数组

arr = [0, 1, 2]
arr.length = 0; // 清空了数组,数组类型不变
// arr = []  // 重新申请了一块空数组对象内存

【推荐学习:javascript高级教程

以上是javascript有gc嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn