浅谈 JavaScript 垃圾回收机制_javascript 有垃圾回收机制-程序员宅基地

技术标签: JavaScript  垃圾回收  

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 中,主要将内存分为新生代和老生代。新生代的对象为存活时间较短的对象,老生代的对象为存活时间较长或常驻内存的对象。

在这里插入图片描述

Scavenge 算法
在分代的基础上,新生代的对象主要通过 Scavenge 算法进行垃圾回收,在 Scavenge 的具体实现中,主要采用了 Cheney 算法。
Cheney 算法是一种采用复制的方式实现的垃圾回收算法,它将堆内存一分为二,每一部分空间称为 semispace。在这两个 semispace 空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的 semispace 空间称为 From 空间,处于闲置状态的空间称为 To 空间。

在这里插入图片描述
当我们分配对象时,先是在 From 空间中进行分配。当开始进行垃圾回收时,会检查 From 空间的存活对象,这些存活对象将被复制到 To 空间中,而非存活对象占用的空间将被释放。
完成复制后,From 空间和 To 空间的角色发生对换。

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

Mark-Sweep & Mark-Compact
老生代中的对象生命周期较长,存活对象占较大比重,V8 在老生代主要采用 Mark-Sweep 和 Mark-Compact 相结合的方式进行垃圾回收
Mark-Sweep:标记清除,其分为标记和清除两个阶段。在标记阶段遍历堆中的所有对象,并标记活着的对象,在清除阶段只清除没有被标记的对象。Mark-Sweep 最大的问题在于进行一次标记清除回收后,内存空间会出现不连续的状态,内存碎片会对后续的内存分配造成问题,比如碎片空间不足以分配一个大对象导致提前触发垃圾回收。
于是就有了 Mark-Compact:标记整理,简单来说就是标记完成后加一个整理阶段,存活对象往一端移动(合并),整理完成后直接清理掉边界外的内存。

在这里插入图片描述
Incremental Marking
为了避免出现 JavaScript 应用逻辑与垃圾回收器看到的不一致的情况,垃圾回收的 3 种基本算法需要将应用逻辑暂停下来,待执行完垃圾回收后再恢复执行应用逻辑,这种行为被称为全停顿(stop-the-world)。
对于新生代来说,全停顿的影响不大,但是对于老生代就需要改善。
为了降低全堆垃圾回收带来的停顿时间,V8 采用了增量标记(incremental marking)的技术,大概是将原本一口气停顿完成的动作拆分为许多小“步进”,每做完一“步进”就让 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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_37205708/article/details/98187869

智能推荐

Oracle Database RAC 11.2.0.4 for AIX6.1安装报irman ioracle错误-程序员宅基地

文章浏览阅读1.6k次。本文转自:http://blog.itpub.net/23135684/viewspace-733990/在AIX平台安装Oracle RAC就是个坑爹的事情,今日再次验证了这点,只因为bug太多!记录问题也只因为bug太多!强烈推荐及看好Oracle RAC在Linux平台的发展。下面对问题的记录:>服务器:IBM Power 740>存储:IBM DS 5020_irman ioracle

华为笔试题—字符串表示数字_华为笔试 字符串-程序员宅基地

文章浏览阅读217次。题目描述:将一个字符中所有出现的数字前后加上符号“*”,其他字符保持不变,例如:输入:Jkdi234klowe90a3输出结果:Jkdi*234*klowe*90*a*3*代码实现:while True: try: num1= [] str1 = input() for i in str1: if i.isdigit() : num1.append('*' + i + '*'_华为笔试 字符串

bzoj 3889: [Usaco2015 Jan]Cow Routing SPFA-程序员宅基地

文章浏览阅读233次。→题目链接←双键值最短路,SPFA代码:#include#include#include#include#define ll long long#define inf 0X3f3f3f3f3f3f3f3fllusing namespace std;struct node{ int to,len1,len2;};int s,e,m,n=0;v_bzoj 3889

Jedis 和 Redis Template 应用_jedis.hval redistemplate-程序员宅基地

文章浏览阅读400次。准备工作创建工程创建maven父工程,例如03-sca-redis,并在此工程下创建两个子工程,一个为sca-jedis,一个为sca-tempate,例如:添加项目依赖sca-jedis 工程依赖<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.5.2</versio_jedis.hval redistemplate

MSP430G2553 PWM控制速度,并自我检测和简单调节速度_ta1ccr0和ta1r-程序员宅基地

文章浏览阅读5.2k次,点赞12次,收藏126次。MSP430G2553 测量小车速度,并简单调速需要了解MSP430系列的GPIO,TIMERA,UART相关的知识涉及内容:PWM输出配置,TIMER A 的捕捉/比较寄存器的输入捕捉配置,串口通信的配置,系统时钟的配置STEP 1 测量小车速度的方法公式:v = s/t;s,就是轮子周长;t就是中断周期PREPARE 1 小车橡胶轮子参数:周长:C=2*pi*R=pi*D=20.41975cm;总共20个洞。1.0209875cm/洞每穿过一个洞,产生一个中_ta1ccr0和ta1r

最简单理解map和flatMap的区别_map和flatmap的区别和理解-程序员宅基地

文章浏览阅读3.0k次,点赞3次,收藏6次。我的理解map:map方法返回的是一个object,map将流中的当前元素替换为此返回值;flatMap:flatMap方法返回的是一个stream,flatMap将流中的当前元素替换为此返回流拆解的流元素;官方解释map:Returns a stream consisting of the results of applying the given function to the ele..._map和flatmap的区别和理解

随便推点

解决编译apache出现的问题:configure: error: APR not found . Please read the documentation-程序员宅基地

文章浏览阅读60次。今日编译apache时出错:#./configure --prefix……检查编辑环境时出现:checking for APR... noconfigure: error:APR not found. Please read the documentation解决办法:1.下载所需软件包:wgethttp://archive.apache.org/dist/apr/..._apache远行出错

android打开office_andoid <office protocol><open mode>|u|<url>-程序员宅基地

文章浏览阅读2.6k次。第一次写博客,请多见谅,写的不好,有什么见解可以跟我说。QQ:1398169857 前段时间需要这么个需求,需要本应用打开word,pdf,ppt等,然后查阅资料,发现有三种方法。一、使用腾讯X5SDK,它提供文件读取能力,自动解析出来,目前只支持本地打开,估计过阵子会支持网上也能打开,而且解析的很好,推荐使用这个。使用步骤如下:①前往https://x5.tencent.co..._andoid |u|

解决highcharts图表在循环里只能显示一个的问题_highchart point.total只显示1个值-程序员宅基地

文章浏览阅读397次。导致原因:1:div的id没有唯一,设置成唯一即可2:highcharts使用了公共配置,设置成私有配置即可_highchart point.total只显示1个值

全新 ADAS 和自动驾驶车辆系统的处理器性能和安全要求_adas是安全要求还是法律法规要求-程序员宅基地

文章浏览阅读5.2k次。简介当今汽车行业的创新正在加速,因为各公司正在竞相成为安全和自动驾驶车辆领域的市场领导者。随着车辆控制正在从人类转变为车辆的主动安全系统,更多传感器 – 摄像头、雷达、激光雷达等 – 正被添加到汽车系统中。更多的传感器需要更大的 SoC 提供的更多计算性能,以处理额外的传入数据,并尽可能降低故障率。因此,自动驾驶汽车要求提高处理性能和安全要求的数量级。这些更大的 SoC 需要最先进的处理器架构,以提供具有最高汽车安全完整性水平 (ASIL) 所需的处理性能。具有传感器的安全系统,其中一个实例是基于雷达的_adas是安全要求还是法律法规要求

echarts中折线标识、线条渐变、虚线设置、双条折线数据个数不同报错问题解决_echarts 折线虚线-程序员宅基地

文章浏览阅读1.8k次。先上整体效果图:1、虚线+折线渐变、折线两端透明度设置如下代码如下://去年虚折线代码//虚线+折线渐变、折线两端透明度设置 代码如下:series: [ { name: "去年", type: "line", symbol: "circle", // 默认是空心圆(中间是白色的),改成实心圆 showAllSymbol: true, symbolSize: 0, smooth: true, lineSt_echarts 折线虚线

sqlsugar_实体命名_sqlsugar 命名规则-程序员宅基地

文章浏览阅读2k次。using SqlSugar;using System;namespace Entity{ /// <summary> /// Base_Card:实体类(属性说明自动提取数据库字段的描述信息) /// </summary> [Serializable] public partial class Base_Card { #region Mode..._sqlsugar 命名规则