JavaScript的内存管理是自动进行的,在创建变量(对象,字符串等)时自动进行了内存分配,之后在代码执行,使用变量时占用这个内存,当不再使用变量后就内存会被回收,释放掉。在JavaScript中,这个过程被称为垃圾回收机制。
什么是内存
计算机硬件由5个部分组成:控制器 、运算器 、存储器 、输入设备 、输出设备 。通常我们所说的内存属于存储器,在程序运行时,cpu需要的调用指令和数据只能从内存中获取(硬盘只有存储功能,执行时会将数据缓存到内存中)。JavaScript只是一种语言,真正进行内存的调用和分配的是JavaScript引擎。
内存的生命周期
不管什么语言,内存的生命周期基本是一致的,一般为以下几个阶段:
- 分配你所需要的内存。
- 使用分配到的内存(读,写)。
- 不需要时将其释放,归还
JavaScript语言中,第一步和第三步是JavaScript引擎自动进行的。
JavaScript引擎
JavaScript引擎是什么
JavaScript引擎是一个专门处理JavaScript脚本的虚拟机。它本质上就是一段程序,可以将JavaScript代码编译为不同CPU对应的汇编代码,此外还负责执行代码,分配内存和垃圾回收等等。
JavaScript引擎的内存结构
JavaScript引擎的内存结构可以粗略分为两个部分:栈(Stack) 、堆(Heap) 。现在市面上比较流行的JavaScript引擎有Google的v8引擎 、Apple的JavaScriptCore等等。不同的引擎它的内存结构有所差别,之后会对v8引擎做个简单的介绍。
栈(Stack)
主要用于存放基本类型和变量类型的指针。栈内存自动分配大小相对固定的内存空间,并由系统自动释放。
堆(Heap)
主要用于存放对象类型数据,如对象,数组,函数等等。堆内存是动态分配内存,内存大小不一,也不会自动释放。
垃圾回收算法
为了更好的回收内存,JavaScript引擎中有一个垃圾回收器(gc),它的主要作用是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。所以问题的重点在于如何判断这个被占用的内存不再被使用,可以被释放掉。JS提供了一系列算法来帮助判断变量是否被引用。
一,引用计数
引用计数是最初的垃圾回收算法,它将"对象是否不再需要"简化定义为"对象有没有其他对象引用到它"如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收
其思路是,每个值都记录它被引用的次数。声明变量并给它赋值时,引用数为1,如果一个值又被赋值给另一个变量,那么引用次数加1。如果引用值被其他的值覆盖了,引用数减1。垃圾回收机制运行时,会回收内存中引用数为0的变量。
示例:
javascript
var o = {
a:{
b:2
}
};
//两个对象被创建,一个对象作为另一个的属性被引用,另一个对象被分配给变量o
//此时,这两个对象的引用数都为1,无法被回收。
//我们通过 ot = 1(o引用); at = 0; 来表示两个对象的引用次数.
//at作为对象ot的属性,且ot还在被引用,暂时不能被回收
var o2 = o; //变量o2再次引用了对象,此时 ot = 2 (o,o2引用) ;at = 0 ;
o = 1; //ot = 1 (o2引用) ;at = 0 ;
var oa = o2.a; // ot = 1 (o2引用) ;at = 1 (oa引用);
o2 = 'ya'; // ot = 0 ;at = 1 (oa引用);
// 此时,虽然ot是零引用,但是它的属性a对象还被引用这,还无法被垃圾回收。
oa = null; // ot = 0;at = 0; 此时这两个对象都没有被引用,可以被垃圾回收了。
但该算法有一个限制,如果出现循环引用就无法回收了。
javascript
function problem(){
let object1 = new Object();
let object2 = new Object();
object1.A = object2;
object2.B = object1; //object1,object2相互引用
}
优化:减少内存的消耗,全局对象的属性或变量不引用时将其值设置为null。
二,标记清除
这个算法把"对象是否不再需要"简化定义为"对象是否可以获得"。它的原理是:在内存中跟踪每个对象的使用情况,并标记所有不再使用的对象,然后,已标记的对象都会被清除,以释放内存。
代码在执行时,变量的取值是从上下文中的取得。在代码的解释阶段,当声明一个变量时,这个变量会加上一个存在于上下文中的标记 。垃圾回收机制运行的时候,会标记内存中存储的所有变量,然后会将所有上下文中存在的变量的标记去掉(上下文中的变量都是代码在后续执行过程中会用到的变量),最后有标记的变量就是待清除的变量。
V8引擎的垃圾回收机制
目前JavaScript最流行的引擎就是V8引擎,它的内存回收机制与传统机制相比又做了许多升级和优化。
v8提出了一个弱分代假说 ,它的垃圾回收机制主要是基于这个假说的。
假说基本思想是:绝大部分的对象生命周期都很短,生命周期很长的对象基本都是常驻对象。
基于这这个假说 v8引擎将堆内存 主要分为新生代 和老生代两个区域(还有一些其他的区域,但垃圾回收主要在这两个区域进行)。新生代主要存储生命周期比较短的对象,老生代则存储生命周期比较长的对象。这两个区域的垃圾回收机制也有所差别。
v8引擎的内存结构
图片来源www.imooc.com/article/300...
v8引擎将堆内存(Heap memory)分为了五个部分:
- 新生代(New space):大多数对象创建时一般都会分配到这块区域,这块垃圾回收较为频繁,经过一次回收依旧存活的对象会放入老生代中。
- 老生代(Old space):新生代的对象存活一段时间就会放入老生代,老生代内存区域垃圾回收频率较低,存放的是生命周期较长的对象。
- 大对象区(Large object space):存放体积超越其他区域大小的对象,垃圾回收不会移动大对象区域。
- 代码区(Code space):这里是即时(JIT)编译器存储编译代码块的地方,即代码对象会被分配到这里,唯一拥有执行权限的内存区域。
- Map区:主要包括单元空间(cell space),属性单元空间(Property cell space),映射空间(Map space)。这些空间中每一个都包含相等大小的对象。
新生代
新生代区域的划分
v8引擎将新生代 划分为两个相等的半空间(Semi space):From space 和 To space 。同一时间只有一个半空间在工作,另一个半空间处于休闲状态 。处于工作状态的半空间叫做 From space,处于休闲状态的半空间叫做To space。
新生代垃圾回收算法
在新生代中,主要使用 Scavenge
算法进行垃圾回收,Scavenge是清除的意思,这是一种典型的以空间换时间的算法。
Scavenge算法的主要思路:代码执行时,程序中首先声明的对象会放到 From space 中,垃圾回收时,将 From space 中非存活对象直接清除 ,存活对象复制到 To space 中 , 然后将这些对象内存有序排列。之后,From space中的内存直接清空。最后,From space 和 To space 完成一次角色互换。To space 会变成新的 From space 空间,From space会变成新的 To sapce空间。
其活动流程如下图:
对象是否存活的判断
这里就要说到一个概念:可达对象。在一个作用域链上,只要通过根可以有路径查找到的对象都是可达对象,也就是之前说的存活对象。在JavaScript中,根可以理解为全局变量对象,也就是window。
新生代对象的晋升
之前说过新生代存储声明周期较短的对象,老生代存储声明周期较长的对象。但代码执行时声明的对象都是放到新生代的 From space 中,所以一个对象在新生代中经过多次复制后还存在,下一次垃圾回收时会将其放入老生代中,这个过程称之为对象的晋升 。
新生代中对象的晋升有两种情况:
- 经过一次 Scavenge 算法(新生代的一次翻转置换过程)在新生代中还存在的对象。
- 在进行Scavenge 算法的复制过程时,如果To space 空间的占比超过25%,则直接将该对象放入老生代中。
25%的内存限制是因为 To space 在经历过一次Scavenge算法后会和 From space 完成角色互换,会变为From空间,后续的内存分配都是在From空间中进行的,如果内存使用过高甚至溢出,则会影响后续对象的分配,因此超过这个限制之后对象会被直接转移到老生代来进行管理。
老生代
老生代垃圾回收算法
和新生代不同,老生代采用的是标记清除(Mark Sweep) 和 标记整理(Mark Compact) 算法进行垃圾回收。
标记清除算法思路 :标记清除算法主要包含标记和清除两个阶段。标记阶段就是从一组根元素开始,遍历递归这组根元素,在这个过程中,能达到的对象就是活动对象,对它们做个标记 。没有达到的对象可以判断为垃圾数据。然后是清除阶段,直接清除没有做标记的对象。
如图所示:
在进行一次标记清除算法后,内存空间可能会出现不连续的情况,也就是内存碎片化问题。如果分配一个大对象可能总内存剩余空间足够,但由于内存碎片化而无法存储。为了解决这个问题提出了标记整理算法,通过移动内存中的可达对象将碎片化内存变成一个整体。
标记整理算法思路:和标记清除类似,标记整理也是先给内存中的可达对象做标记,然后在将其整理排序。
参考文章
赠你13张图,助你20分钟打败了「V8垃圾回收机制」!!! - 掘金 (juejin.cn)
一文搞懂V8引擎的垃圾回收 - 掘金 (juejin.cn)
V8引擎详解(六)------内存结构 - 掘金 (juejin.cn)
V8引擎的内存管理_慕课手记 (imooc.com)