浅谈 JavaScript 垃圾回收机制

时间:2022-12-13 07:39:11

github 获取更多资源

https://github.com/ChenMingK/WebKnowledges-Notes

在线阅读:https://www.kancloud.cn/chenmk/web-knowledges/1080520

垃圾回收机制

对垃圾回收算法而言,其核心思想就是如何判断内存不再使用了

比较古老的说法是 引用计数标记清除

引用计数

引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需了。

// 创建一个对象 person,他有两个指向属性 age 和 name 的引用
var person = {
age: 12,
name: 'aaaa'
}; person.name = null // 虽然设置为null,但因为 person 对象还有指向 name 的引用,因此name 不会回收 var p = person
person = 1 // 原来的 person 对象被赋值为 1,但因为有新引用 p 指向原 person 对象,因此它不会被回收
p = null // 原 person 对象已经没有引用,很快会被回收

由上面可以看出,引用计数算法是个简单有效的算法。但它却存在一个致命的问题:循环引用。如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。比如下面这样

function cycle () {
var o1 = {}
var o2 = {}
o1.a = o2
o2.a = o1
return "Cycle reference!"
}
cycle()

标记清除

标记清除算法将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。但反之未必成立。

V8引擎垃圾回收机制

可以阅读这篇文章,最近看 《深入浅出 Node.js》淘到些 V8 垃圾回收机制的介绍。

V8 的垃圾回收机制与内存限制

在一般的后端开发语言中,基本的内存使用上没有什么限制,然而在 Node 中通过 JavaScript 使用内存时会发现只能使用部分内存(64 位系统下约为 1.4 GB,32 位系统下约为 0.7 GB)。在这样的限制下,将会导致 Node 无法直接操作大内存对象,比如无法将一个 2GB 的文件读入内存中进行字符串分析处理。(stream 模块解决了这个问题)

造成这个问题的主要原因在于 Node 基于 V8 构建,V8 的内存管理机制在浏览器的应用场景下绰绰有余,但在 Node 中却限制了开发者。所以我们有必要知晓 V8 的内存管理策略。

V8 的对象分配

在 V8 中,所有的 JavaScript 对象(object)都是通过堆来进行分配的,Node 提供了 V8 中内存使用量的查看方式,如下:

process.memoryUsage()
{ rss: 21434368,
heapTotal: 7159808,
heapUsed: 4455120,
external: 8224 }

其中,heapTotal 和 heapUsed 是 V8 的堆内存使用情况,前者是已申请到的堆内存,后者是当前使用的量。如果已申请的堆空闲内存不够分配新的对象,将继续申请堆内存,直到堆的大小超过 V8 的限制为止。

至于 V8 为何要限制堆的大小,主要是内存过大会导致垃圾回收引起 JavaScript 线程暂停执行的时间增长,应用的性能和响应会直线下降,这样的情况不仅仅是后端服务无法接受,前端浏览器也无法接受。因此,在当时的考虑下直接限制堆内存是一个好的选择。

不过 V8 也提供了选项让我们打开这个限制,Node 在启动时可以传递如下的选项:

node --max-old-space-size=1700 test.js // 单位为 MB 设置老生代的内存空间
node --max-new-space-size=1024 test.js // 单位为 KB 设置新生代的内存空间

上述参数在 V8 初始化时生效,一旦生效就不能再改变。

V8 的垃圾回收机制

V8 的垃圾回收策略主要基于分代式垃圾回收机制,在实际应用中,人们发现没有一种垃圾回收算法能够胜任所有的场景,因为对象的生存周期长短不一,不同的算法只能针对特定情况具有最好的效果。因此,现代的垃圾回收算法按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同分代的内存施以更高效的算法。

在 V8 中,主要将内存分为新生代和老生代。新生代的对象为存活时间较短的对象,老生代的对象为存活时间较长或常驻内存的对象。

浅谈 JavaScript 垃圾回收机制

Scavenge 算法

在分代的基础上,新生代的对象主要通过 Scavenge 算法进行垃圾回收,在 Scavenge 的具体实现中,主要采用了 Cheney 算法。

Cheney 算法是一种采用复制的方式实现的垃圾回收算法,它将堆内存一分为二,每一部分空间称为 semispace。在这两个 semispace 空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的 semispace 空间称为 From 空间,处于闲置状态的空间称为 To 空间。

浅谈 JavaScript 垃圾回收机制

当我们分配对象时,先是在 From 空间中进行分配。当开始进行垃圾回收时,会检查 From 空间的存活对象,这些存活对象将被复制到 To 空间中,而非存活对象占用的空间将被释放。

完成复制后,From 空间和 To 空间的角色发生对换。

  • Scavenge 的缺点是只能使用堆内存中的一半
  • Scavenge 是典型的牺牲空间换取时间的算法,适合应用于新生代中,因为新生代中对象的生命周期较短
  • 当一个对象经过多次复制仍然存活时,它将会被认为是生命周期较长的对象,其随后会被移动到老生代中,这一过程称为晋升

Mark-Sweep & Mark-Compact

老生代中的对象生命周期较长,存活对象占较大比重,V8 在老生代主要采用 Mark-Sweep 和 Mark-Compact 相结合的方式进行垃圾回收

Mark-Sweep:标记清除,其分为标记和清除两个阶段。在标记阶段遍历堆中的所有对象,并标记活着的对象,在清除阶段只清除没有被标记的对象。Mark-Sweep 最大的问题在于进行一次标记清除回收后,内存空间会出现不连续的状态,内存碎片会对后续的内存分配造成问题,比如碎片空间不足以分配一个大对象导致提前触发垃圾回收。

于是就有了 Mark-Compact:标记整理,简单来说就是标记完成后加一个整理阶段,存活对象往一端移动(合并),整理完成后直接清理掉边界外的内存。

浅谈 JavaScript 垃圾回收机制

Incremental Marking

为了避免出现 JavaScript 应用逻辑与垃圾回收器看到的不一致的情况,垃圾回收的 3 种基本算法需要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为全停顿(stop-the-world)。

对于新生代来说,全停顿的影响不大,但是对于老生代就需要改善。

为了降低全堆垃圾回收带来的停顿时间,V8 采用了增量标记(incremental marking)的技术,大概是将原本一口气停顿完成的动作拆分为许多小“步进”,每做完一“步进”就让 JavaScript 应用逻辑执行一小会儿,垃圾回收与应用逻辑交替执行直到标记阶段完成。

浅谈 JavaScript 垃圾回收机制

V8 后续还引入了延迟清理(lazy sweeping)、增量式整理(incremental compaction)、并发标记 等技术,感兴趣的可以自行了解。

查看垃圾回收日志

启动时添加 --trace_gc 参数,这样在进行垃圾回收时,将会从标准输出中打印垃圾回收的日志信息。

下面是一段示例,执行结束后,将会在 gc.log 文件中得到所有垃圾回收信息:

node --trace_gc -e "var a = []; for (var i = 0; i < 1000000; i++) a.push(new Array(100));" > gc.log

通过在 Node 启动时使用 --prof 参数,可以得到 V8 执行时的性能分析数据:

node --prof test.js

浅谈 JavaScript 垃圾回收机制的更多相关文章

  1. 浅谈python垃圾回收机制

    引入 ​ 解释器在执行到定义变量的语法时,会申请内存空间来存放变量的值,而内存的容量是有限的,这就涉及到变量值所占用内存空间的回收问题,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,那 ...

  2. 浅谈java垃圾回收机制

    今天看thinking in java,里面很详细的谈到java垃圾回收器机制,看完后让我对这神秘的区域有一定的了解,特写一些小总结记录下来. 分两点来说. 第一点:Object.finalize() ...

  3. 浅谈c&num;垃圾回收机制(GC)

    写了一个window服务,循环更新sqlite记录,内存一点点稳步增长.三天后,内存溢出.于是,我从自己的代码入手,查找到底哪儿占用内存释放不掉,最终明确是调用servicestack.ormlite ...

  4. JavaScript 垃圾回收机制分析

    同C# .Java一样可以手工调用垃圾回收程序,但是由于其消耗大量资源,而且手工调用的不会比浏览器判断的准确,所以不推荐手工调用垃圾回收.   最近精力主要用在了Web 开发上,读了一下<Jav ...

  5. Javascript垃圾回收机制(学习笔记)

    1,javascript具有自动的垃圾回收机制,自动内存的分配和无用内存的回收都可以自动管理.垃圾回收器周期性的执行: 2,Javascript的垃圾回收策略分为:引用计数和标记清除: 2.1 标记清 ...

  6. Javascript 垃圾回收机制

    转载于https://www.cnblogs.com/zhwl/p/4664604.html 一.垃圾回收的必要性 由于字符串.对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储 ...

  7. JS 从内存空间谈到垃圾回收机制

     壹 ❀ 引 从事计算机相关技术工作的同学,对于内存空间相关概念多少有所耳闻,毕竟像我这种非计算机科班出身的人,对于栈堆,垃圾回收都能简单说道几句:当我明白JS 基本类型与引用类型数据存储方式不同,才 ...

  8. 简单梳理JavaScript垃圾回收机制

    JavaScript具有自动垃圾回收机制,即执行环境会负责管理代码执行过程中使用地内存. 这种垃圾回收机制的原理很简单:找出那些不再继续使用的变量,然后释放其占用的内存.为此,垃圾收集器会按照固定的时 ...

  9. 浅谈JVM垃圾回收

    JVM内存区域 要想搞懂啊垃圾回收机制,首先就要知道垃圾回收主要回收的是哪些数据,这些数据主要在哪一块区域. Java8和Java8之前的相同点有很多. 都有虚拟机栈,本地方法栈,程序计数器,这三个是 ...

随机推荐

  1. 自定义一个只显示年月的DatePicker(UIDatePicker无法实现年月显示)

    HooDatePicker 介绍(introduction) ==================================================项目需要一个DatePicker,只显 ...

  2. CSS3-transform&comma;2D动画实例

    对元素进行移动.缩放.转动.拉长 或 拉伸 全部都需要加前缀. Transform-2D转换方法:rotate()旋转.scale()缩放.skew()扭曲/倾斜.translate()位移.matr ...

  3. Struts2 标签分类

  4. CentOS 6&period;4 安装搭建 Scrapy 0&period;22 环境

    一.安装Python2.7.6 更新CentOS lib库文件 yum -y update 安装开发工具包 yum groupinstall -y development 安装扩展包 yum inst ...

  5. Java为什么会超时

     java程序,最终是转成c运行的,我们写的程序会转成c代码,并且由底层的c程序调用,可以理解成我们程序最后变成被调用的c函数  底层的C程序是java虚拟机,由它读取我们的.class文件,翻译成c ...

  6. phaser源码解析(三) Phaser&period;Utils类下isPlainObject方法

    /** * #这是一个对jQuery.isPlainObject(obj)稍加修改的方法. 一个 普通对象 obj.toString() => "[object Object]&quo ...

  7. java中String的&period;trim&lpar;&rpar;方法

    该方法去除两边的空白符 原理: 看看源码实现 public String trim() { int len = value.length; ; char[] val = value; /* avoid ...

  8. 【Unity3D】Unity3D开发《我的世界》之五、创建无限地形(视频)

    转载请注明出处:http://www.cnblogs.com/shamoyuu/p/unity_minecraft_05.html 一.导入Unity3D自带的第一人称角色控制器 直接导入就行,我们用 ...

  9. HCharts随笔之简单入门

    此处可以对比我的另一个Echars简单入门 直接上源码 <!DOCTYPE html> <html> <head> <meta http-equiv=&quo ...

  10. HDU6446 Tree and Permutation(树上DP)

    传送门:点我 Tree and Permutation Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (J ...