V8如何进行垃圾回收的

简介: V8如何进行垃圾回收的

1. 前言

  1. js垃圾回收机制
  2. MDN 垃圾回收机制
  3. 这是原文,支持原创,可以直接点进进去 忽略掉本文的
  4. 自己还在搬运一遍的目的就是为了熟悉,也为了以后自己查看,保存链接的,说不定TA人的链接啥时候不能用了

2. 是什么 what

不管什么程序语言,内存生命周期基本是一致的:

1.分配你所需要的内存

2.使用分配到的内存(读、写)

3.不需要时将其释放\归还

在 JavaScript 中,当创建变量时,系统会自动给对象分配对应的内存,来看下面的例子:

var n = 123; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存
var o = {
  a: 1,
  b: null
}; // 给对象及其包含的值分配内存
// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"];
function f(a){
  return a + 2;
} // 给函数(可调用的对象)分配内存
// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue';
}, false);

3.  JavaScript 数据分类

  1. 基本类型:这些类型在内存中会占据固定的内存空间,它们的值都保存在栈空间中,直接可以通过值来访问这些;
  2. 引用类型:由于引用类型值大小不固定,栈内存中存放地址指向堆内存中的对象,是通过引用来访问的。

栈内存和堆内存

  1. 栈内存中的基本类型,可以通过操作系统直接处理;
  2. 而堆内存中的引用类型,正是由于可以经常变化,大小不固定,因此需要 JavaScript 的引擎通过垃圾回收机制来处理。

垃圾回收

  1. 所谓的垃圾回收是指:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间。
  2. Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。

局部变量和全局变量

  1. JavaScript中存在两种变量:局部变量和全局变量。
  2. 全局变量的生命周期会持续到页面卸载
  3. 局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束
  4. 在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放
  5. 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收

4. V8 垃圾回收

4.1 通过 GC Root 标记空间中活动对象和⾮活动对象

  1. ⽬前 V8 采⽤的可访问性算法来判断堆中的对象是否是活动对象。这个算法是将⼀些 GC Root 作为初始存活的对象的集合,从 GC Roots 对象出发,遍历 GC Root 中所有对象
  2. 通过 GC Root 遍历到的对象是可访问的,必须保证这些对象应该在内存中保留,可访问的对象称为活动对象
  3. 通过 GC Roots 没有遍历到的对象是不可访问的,这些不可访问的对象就可能被回收,不可访问的对象称为⾮活动对象
  4. 这也是自 2012年起 所有浏览器都使用的标记清除算法

4.2  回收⾮活动对象所占据的内存

  1. 其实就是在所有的标记完成之后,统⼀清理内存中所有被标记为可回收的对象

4.3 内存整理

  1. ⼀般来说,频繁回收对象后,内存中就会存在⼤量不连续空间,这些不连续的内存空间称为内存碎⽚。当内存中出现了⼤量的内存碎⽚之后,如果需要分配较⼤的连续内存时,就有可能出现内存不⾜的情况,所以最后⼀步需要整理这些内存碎⽚。这步其实是可选的,因为有的垃圾回收器不会产⽣内存碎⽚。
  2. 以上就是⼤致的垃圾回收流程。⽬前 V8 使用了两个垃圾回收器:主垃圾回收器和副垃圾回收器。下面就来看看 V8 是如何实现垃圾回收的。
  3. 在 V8 中,会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放生存时间久的对象:
  4. 图示image.png
  5. 新⽣代通常只⽀持1~8M的容量,⽽⽼⽣代⽀持的容量就⼤很多。对于这两块区域,V8分别使⽤两个不同的垃圾回收器,以便更⾼效地实施垃圾回收:

副垃圾回收器:负责新⽣代的垃圾回收。

主垃圾回收器:负责⽼⽣代的垃圾回收。


5. 副垃圾回收器--新生代

  1. 副垃圾回收器主要负责新⽣代的垃圾回收。大多数的对象最开始都会被分配在新生代,该存储空间相对较,分为两个空间:from 空间(对象区)和 to 空间(空闲区)
  2. 新加⼊的对象都会存放到对象区域,当对象区域快被写满时,就需要执⾏⼀次垃圾清理操作:首先要对对象区域中的垃圾做标记,标记完成之后,就进入垃圾清理阶段。副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来。这个复制过程就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了:
  3. 图示image.png
  4. 完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域,这种算法称之为 Scavenge 算法,这样就完成了垃圾对象的回收操作。同时,这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去:
  5. 图示image.png
  6. 不过,副垃圾回收器每次执⾏清理操作时,都需要将存活的对象从对象区域复制到空闲区域,复制操作需要时间成本,如果新⽣区空间设置得太⼤了,那么每次清理的时间就会过久,所以为了执⾏效率,⼀般新⽣区的空间会被设置得⽐较⼩。 也正是因为新⽣区的空间不⼤,所以很容易被存活的对象装满整个区域,副垃圾回收器⼀旦监控对象装满了,便执⾏垃圾回收。同时,副垃圾回收器还会采⽤对象晋升策略,也就是移动那些经过两次垃圾回收依然还存活的对象到⽼⽣代中

6. 主垃圾 回收器  ---老生代

  1. 主垃圾回收器主要负责⽼⽣代中的垃圾回收。除了新⽣代中晋升的对象,⼀些⼤的对象会直接被分配到⽼⽣代⾥。因此,⽼⽣代中的对象有两个特点:
  2. 对象占⽤空间⼤;
  3. 对象存活时间⻓。

由于⽼⽣代的对象⽐较⼤,若要在⽼⽣代中使⽤ Scavenge算法进⾏垃圾回收,复制这些⼤的对象将会花费较多时间,从⽽导致回收执⾏效率不⾼,同时还会浪费空间。所以,主垃圾回收器采⽤标记清除的算法进⾏垃圾回收。

这种方式分为标记和清除两个阶段:

  1. 标记阶段: 从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
  2. 清除阶段: 主垃圾回收器会直接将标记为垃圾的数据清理掉。
  3. 2个阶段如图image.png
  4. 对垃圾数据进⾏标记,然后清除,这就是标记清除算法,不过对⼀块内存多次执⾏标记清除算法后,会产⽣⼤量不连续的内存碎⽚。⽽碎⽚过多会导致⼤对象⽆法分配到⾜够的连续内存,于是⼜引⼊了另外⼀种算法——标记整理。
  5. 这个算法的标记过程仍然与标记清除算法⾥的是⼀样的,先标记可回收对象,但后续步骤不是直接对可回收对象进⾏清理,⽽是让所有存活的对象都向⼀端移动,然后直接清理掉这⼀端之外的内存:
  6. 图示 image.png

7. 全停顿

  1. 我们知道,JavaScript 是单行线语言,运行在主线程上。一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。这种行为叫做全停顿
  2. 主垃圾回收器执行一次完整的垃圾回收流程如下图所示:
  3. image.png
  4. 在 V8 新生代的垃圾回收中,因其空间较小,且存活对象较少,所以全停顿的影响不大。但老生代中,如果在执行垃圾回收的过程中,占用主线程时间过久,主线程是不能做其他事情的,需要等待执行完垃圾回收操作才能做其他事情,这将就可能会造成页面的卡顿现象。
  5. 为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,这个算法称为增量标记算法。如下图所示image.png
  6. 使用增量标记算法可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样当执行代码时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。

8. 减少垃圾回收

  1. 虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价较大,所以应该尽量减少垃圾回收:
  2. 对数组进行优化: 在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的。
  3. 对object进行优化: 对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收。
  4. 对函数进行优化: 在循环中的函数表达式,如果可以复用,尽量放在函数的外面

参考资料

js垃圾回收机制

MDN 垃圾回收机制


初心

我所有的文章都只是基于入门,初步的了解;是自己的知识体系梳理,如有错误,道友们一起沟通交流;
如果能帮助到有缘人,非常的荣幸,一切为了部落的崛起;
共勉
相关文章
|
3月前
|
存储 Java 程序员
V8垃圾回收?看这篇就够了!
V8垃圾回收?看这篇就够了!
|
存储 Web App开发 监控
Js中的垃圾回收及V8引擎的优化
Js中的垃圾回收及V8引擎的优化
272 0
|
缓存 JavaScript 前端开发
图解 Google V8 # 22 :关于内存泄漏、内存膨胀、频繁垃圾回收的解决策略(完结篇)
图解 Google V8 # 22 :关于内存泄漏、内存膨胀、频繁垃圾回收的解决策略(完结篇)
355 0
图解 Google V8 # 22 :关于内存泄漏、内存膨胀、频繁垃圾回收的解决策略(完结篇)
|
Web App开发 JavaScript 前端开发
图解 Google V8 # 21 :垃圾回收(二):V8是如何优化垃圾回收器执行效率的?
图解 Google V8 # 21 :垃圾回收(二):V8是如何优化垃圾回收器执行效率的?
139 0
图解 Google V8 # 21 :垃圾回收(二):V8是如何优化垃圾回收器执行效率的?
|
算法 JavaScript Java
图解 Google V8 # 20 :垃圾回收(一):V8的两个垃圾回收器是如何工作的?
图解 Google V8 # 20 :垃圾回收(一):V8的两个垃圾回收器是如何工作的?
121 0
图解 Google V8 # 20 :垃圾回收(一):V8的两个垃圾回收器是如何工作的?
|
存储 前端开发 算法
V8 如何进行垃圾回收?
V8 如何进行垃圾回收?
93 0
ADI
|
算法 Java
[记录]简单聊聊 v8 垃圾回收
[记录]简单聊聊 v8 垃圾回收
ADI
96 0
|
算法 Java
|
6天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
11天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制(GC)
本文将探讨Java的自动内存管理核心——垃圾回收机制。通过详细解析标记-清除算法、复制算法和标记-整理算法等常用垃圾回收算法,以及CMS、G1等常见垃圾回收器,帮助读者更好地理解Java应用的性能优化和内存管理。同时,探讨分代收集、分区收集等策略在实际项目中的应用。结语部分总结了垃圾回收机制在Java开发中的重要性,并展望了未来可能的发展。
16 0