js中的垃圾回收机制

1.什么是堆、栈

一种数据结构,栈有先进后出的特性, 堆是用来存放复杂数据类型的地址(栈用来存放它的引用)

2、执行上下文与作用域链

执行上下文:全局执行上下文、函数执行上下文、Eval 函数执行上下文

凡是未被引用的对象或变量,都会被视为垃圾

3、js垃圾回收的两种机制

  • 标记清除
  • 引用计数

标记清除

当变量进入上下文时,会对其添加上 存在于上下文 的标记。当变量退出上下文时,对退出上下文的变量添加上退出上下文的标记

例如在一个函数中声明一个变量,该变量就会被标记为存在于上下文中。当函数执行完毕,上下文栈
弹出该函数的上下文,其内变量添加 退出上下文的标记。

此种策略的垃圾回收机制在运行的时候,会对所有已存在于内存的变量进行标记。

之后垃圾回收机制会清除上下文中所有变量的标记,包括其引用的变量的标记也会在此被清除。

最后仍然被标记的变量,即为要回收的垃圾。因为没有地方引用他们。

引用计数

该种策略会对每个值记录它被引用的次数。声明变量并给他赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,则引用次数+1。

类似的,如果保存对该值引用的变量被其他值给覆盖了,那么引用数-1。

当一个值的引用数为0时,就说明没有办法在访问到这个值了。此时被判断为垃圾,在下次垃圾回收机制执行时会释放引用值为0的值所占用的内存。

本质上都是找到未被引用的值,从而在垃圾回收的时候释放空间

4、什么时候执行垃圾回收
不同浏览器执行垃圾回收的时机不一样,对于大部分浏览器来说我们不能人为的控制什么时候进行垃圾回收,因为js并没有暴露相关的接口供我们调用

不再需要内存时释放
大多数内存管理问题发生在这个阶段。此阶段最困难的方面是确定何时不再需要分配的内存。 低级语
言要求开发人员手动确定程序中哪个点不再需要分配的内存并释放它。 一些高级语言,例如
JavaScript,使用一种称为垃圾收集 (GC) 的自动内存管理形式。垃圾收集器的目的是监控内存分配并
确定何时不再需要分配的内存块并回收它。这个自动过程是一个近似值,因为确定是否仍然需要特定
内存的一般问题是不可判定的。

在拥有了两种垃圾回收策略后,执行的周期性不再是问题,因为我们能够将垃圾明确出来,只需要等下次回收即可。

周期不再是问题

function f() {var x = {};
var y = {};
x.a = y;        // x references y
y.a = x;        // y references xreturn 'azerty';
}
f();

在上面的示例中,函数调用返回后,这两个对象不再被可从全局对象访问的任何资源引用。因此,垃圾收集器将发现它们无法访问并回收分配的内存。

关于Chrome V8引擎的GC

分代回收

大多数对象的生存周期很短,小部分对象的生存周期比较长,利用这一特性,V8对它们进行了分区管理

  • 新生区:大多数对象被分配在这里。新生区是一个很小的区域,垃圾回收在这个区域非常频繁,与其他区域相独立。
  • 老生指针区:这里包含大多数可能存在指向其他对象的指针的对象。大多数在新生区存活一段时间之后的对象都会被挪到这里。
  • 老生数据区:这里存放只包含原始数据的对象(这些对象没有指向其他对象的指针)。字符串、封箱的数字以及未封箱的双精度数字数组,在新生区存活一段时间后会被移动到这里。
  • 大对象区:这里存放体积超越其他区大小的对象。每个对象有自己mmap产生的内存。垃圾回收器从不移动大对象。
  • 代码区:代码对象,也就是包含JIT之后指令的对象,会被分配到这里。这是唯一拥有执行权限的内存区(不过如果代码对象因过大而放在大对象区,则该大对象所对应的内存也是可执行的。译注:但是大对象内存区本身不是可执行的内存区)。
  • Cell区、属性Cell区、Map区:这些区域存放Cell、属性Cell和Map,每个区域因为都是存放相同大小的元素,因此内存结构很简单。

回收的执行周期

对象起初会被分配在新生区(通常很小,只有1-8 MB,具体根据行为来进行启发)。在新生区的内存分配非常容易:我们只需保有一个指向内存区的指针,不断根据新对象的大小对其进行递增即可。当该指针达到了新生区的末尾,就会有一次清理(小周期),清理掉新生区中不活跃的死对象。

对于活跃超过2个小周期的对象,则需将其移动至老生区。老生区在标记-清除或标记-紧缩(大周期)的过程中进行回收。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。至于足够多到底是多少,则根据老生区自身的大小和程序的动向来定。

Scavenge算法

V8采用了Scavenge算法,是按照Cheney的算法实现的。

算法的大致流程为:将新生区划分为入区(from-space)和出区(to-space)。绝大多是内存分配是在出区进行,而当出区被填满时,我们会交换出区和入区,然后将入区中活跃的对象复制至出区或老生区当中。在这时我们会对活跃对象进行紧缩,以便提升Cache的内存局部性,保持内存分配的简洁快速。


上图描述了在新生区中,如何回收的垃圾b。

而当一个变量在两次从入区(from-space) 移动到 出区(to-space) 时。他就会被提升到老生区的内存空间中

注意,在上面的回收过程中,为了避免有老生区的变量指向新生区,但在新生区的清理周期中被引用的变量被错误回收,V8引擎做了额外的处理:写屏障

写屏障

肯定不可能通过遍历老生区去查找到底哪个变量引用了新生区的变量,耗时太大。所以通过在写缓冲区中创建一个列表去记录所有老生区对象指向新生区的情况,这样就可以避免上述错误回收。该记录行为总是发生在写操作的时候,每个写操作都会经历这么一关。

老生区

在老生区中,用到的是上文我们说过的标记清理法结合标记紧缩法去回收。

标记清理法是如何标记的
V8 使用每个对象的两个 mark-bits 和一个标记工作表来实现标记。两个 mark-bits 编码三种颜色:白色(00),灰色(10)和黑色(11)。

如果一个对象的状态为白,那么它尚未被垃圾回收器发现,同时最开始所有对象都是白色
如果一个对象的状态为灰,那么它已被垃圾回收器发现,但它的邻接对象仍未全部处理完毕
如果一个对象的状态为黑,则它不仅被垃圾回收器发现,而且其所有邻接对象也都处理完毕

算法的核心实际是深度优先搜索,从根(Root)可达的对象会被染为灰色,并放入标记用的一个单独分配的双端队列。标记阶段的每次循环,GC会将一个对象从双端队列中取出,染为黑色,然后将它的邻居对象染为灰色,并把邻居对象放入双端队列。这一过程在双端队列为空且所有对象都变黑时结束。

特别大的对象,如长数组,可能会在处理时分片,以防溢出双端队列。如果双端队列溢出了,则对象仍然会被染为灰色,但不会再被放入队列(这样他们的邻接对象就没有机会再染色了)。因此当双端队列为空时,GC仍然需要扫描一次,确保所有的灰对象都成为了黑对象。对于未被染黑的灰对象,GC会将其再次放入队列,再度处理。

标记算法结束时,所有的活跃对象都被染为了黑色,而所有的死对象则仍是白的。

标记紧缩法

在使用完标记清理法后,确实能够将垃圾清理掉,但是清理后的空间是不连续的。而一些数据的存储要求的是连续的空间,所以这时候就需要用标记紧缩法去整理碎片空间。

达到这种效果

增量标记法

当一个堆很大而且有很多活跃对象时,标记-清除和标记-紧缩算法会执行的很慢,又因为垃圾回收机制在执行时会阻塞js代码(JS是单线程的),所以在2012年年中,谷歌引入了增量标记和惰性清理两项技术。

增量标记允许堆的标记发生在几次5-10毫秒(移动设备)的小停顿中。增量标记在堆的大小达到一定的阈值时启用。启用后每当一定量的内存分配后,脚本就会停顿一次用来执行标记,同样是黑白灰三色,也同样是深度优先搜索。

写屏障

和上文提到过的写屏障类似,为了避免出现黑色指向白色这种情况出现,我们通过写屏障记录黑色指向白色的指针,一旦发现这种指针,就会将黑色对象重新染色为灰色对象,重新放回到双端队列中。当算法将该对象取出时,其包含的指针会被重新扫描,这样活跃的白对象就不会漏掉。

惰性清理

因为所有对象已被处理,因此非死即活。谁是垃圾已经很明确了,所以不用着急释放空间,延迟一下清理也可以。

效果类似下图所示

上面的是完整的GC执行,下方的是增量标记法与惰性清理的执行。当清理完后,即可随时开始再一次的标记。

并发标记与并行标记

并发标记支持在主线程进行GC的时候启动多个worker thread一起执行GC。应用程序在整个并发标记阶段暂停,它是 stop-the-world 标记的多线程版本。

并行标记则是在主线程还在运行时即可启动多个worker thread执行GC,应用程序可以继续运行。

4、内存问题

内存泄漏
内存泄漏指的是在执行垃圾回收的时候, 由于一些原因导致本应释放掉的空间没有被释放掉。

常见的引起内存的方法

循环引用

在浏览器早起采用引用计数法的时候,如果两个变量相互引用,则其引用数始终为1,而垃圾回收只会对引用数为0的变量进行回收,这时就导致了内存泄漏。这也是为什么现在大都采用的标记清理法

没有被销毁的全局变量和计时器

function fn(){bar = 'bar'; // 声明了全局变量
}
fn();var timer = getStart();
getStart(function() {var temp = document.getElementById('temp');if(temp) {temp.innerHTML = JSON.stringify(temp);}
}, 5000); // 每5秒调用一次

此时若不手动置为null/调用clearInterval,则该变量和计时器将会一直存在,造成内存泄漏。直到window对象被销毁。

闭包

var closure = function(){var count = 0;return function(){return count++}
}
const fn = closure();

由于被返回的函数一直持有其外层函数closure的变量count导致count无法被回收,造成内存泄漏。所以能少用闭包就少用,或者用完及时置为null。

内存溢出

内存溢出是一种程序运行的错误。指的是当程序运行需要的内存超过了剩余内存的时候,就会抛出内存溢出的错误。

内存泄漏积累过多时,就会导致内存溢出

参考链接:https://blog.csdn.net/qq_45670042/article/details/121181328

js中的垃圾回收机制相关推荐

  1. 浅谈V8引擎中的垃圾回收机制

    浅谈V8引擎中的垃圾回收机制 这篇文章的所有内容均来自 朴灵的<深入浅出Node.js>及A tour of V8:Garbage Collection,后者还有中文翻译版V8 之旅: 垃 ...

  2. 简述python垃圾回收机制_python中的垃圾回收机制简述

    2020年12月5日21:47:35 王凯玉 python中的垃圾回收机制 引用计数 # 引用计数 引用计数是编程语言中的一中内存管理技术,可以将资源的被引用次数保存起来. 当引用计数为0时,资源将被 ...

  3. Java Jvm 中的垃圾回收机制中的思想与算法 《对Java的分析总结》-四

    Java中的垃圾回收机制中的思想与算法 <对Java的分析总结>-四 垃圾回收机制 中的思想与算法 引用计算法 给对象中添加一个引用计数器,每当一个地方引用它的时候就将计数器加1,当引用失 ...

  4. 一篇文章搞定java中的垃圾回收机制面试题

    一篇文章搞定java中的垃圾回收机制面试题 任何语言在运行过程中都会创建对象,也就意味着需要在内存中为这些对象在内存中分配空间,在这些对象失去使用的意义的时候,需要释放掉这些内容,保证内存能够提供给新 ...

  5. Chrome V8系列--浅析Chrome V8引擎中的垃圾回收机制和内存泄露优化策略

    V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制.因此,V8 将内存(堆)分为新生代和老生代两部分. 一.前言 V8的垃圾回收机制:JavaScript使用垃圾回收机制来自动管理内存.垃圾 ...

  6. JAVA中的垃圾回收机制以及其在android开发中的作用

    http://blog.csdn.net/xieqibao/article/details/6707519 这篇文章概述了JAVA中运行时数据的结构,以及垃圾回收机制的作用.在后半部分,描述了如何检测 ...

  7. python有向图_Python 中的垃圾回收机制

    一.概述 python采用的是引用计数机制为主,标记-清除和分代收集(隔代回收)两种机制为辅的策略. 现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的 ...

  8. java和net共同点,Java和.NET中的垃圾回收机制比较

    相同点: 都采用了分代的机制. 都支持并发GC. 都没有采用引用计数方式,而是采用了追踪技术. .NET中,可以通过代码GC.Collect() 强制要求CLR进行垃圾回收(由于垃圾回收是异步的,CL ...

  9. python 垃圾回收哪时候执行_Python 中的垃圾回收机制是如何工作的?

    CPython 中垃圾回收的主要思路 1.维护引用计数器 .对于每一个对象,都有一个对于该对象的引用次数的计数器.如果这个计数器的值减为了 0 ,这就代表这个对象在程序中已经没用了,那么该对象所占用的 ...

最新文章

  1. 实战:人脸识别的Arcface实现 | CSDN博文精选
  2. NLP命名实体识别NER数据准备及模型训练实例
  3. python实现二分查找(折半查找)算法
  4. golang runes 字符串 互转
  5. oracle clob 次数,解决Oracle clob字段数据过大问题
  6. 马尔可夫链算法原理与实现
  7. Swift中为什么输入“..”报错
  8. 数据结构与算法--翻转单词顺序
  9. Python 小白从零开始 PyQt5 项目实战(3)信号与槽的连接
  10. C#中 类和结构 值类型和引用类型以及 值传递和引用传递
  11. 对称二叉树--深度遍历与广度遍历
  12. VS2015 编译问题记录(更新)
  13. 溢出植入型木马(后门)的原型实现 作者:FLASHSKY(原创)
  14. iTextSharp笔记
  15. 数据结构_树状数组 详解
  16. EXCEL如何生成不重复的随机数 ,多方法+原理
  17. Uber上市即破发 CEO安慰员工:Facebook和亚马逊上市后股价表现也不好
  18. ISO27017云服务信息安全管理体系认证
  19. 微信小程序对商户而言到底有什么用?
  20. zk服务启动报错:Unexpected exception, exiting abnormally.java.io.IOException:

热门文章

  1. 《大型网站技术架构:核心原理与案例分析》拜读总结,第八章——固若金汤:网站的安全架构
  2. 我的目标,我的梦想,我的坚持---给自己一个见证
  3. 2020年总结---不要因为走的太远,而忘记当初,为什么出发
  4. GitHub 热榜:一个能将文本快速转化为代码的 Python 神器!
  5. .flo转换.png的好工具
  6. 计算机技术在设计中的应用浅论,论计算机技术在美术设计中的应用
  7. 敬回忆一杯酒,来祭奠我那逝去的青春。
  8. Abbkine 细胞侵袭分析试剂盒,简单方便,快速检测
  9. 计算e=1+1/1!+1/2!+...当1/n!<1e-7停止
  10. qnx 设备驱动开发_QNX操作系统及网络设备驱动模块