w3c中javascript的内存管理
内存生命周期
大部分语言的内存生命周期都是基本一致的:
- 分配所需的内存
- 使用内存,读写内存
- 不需要时释放内存
而在javascript中,不需要手动进行内存分配,会在定义变量的时候就完成了内存的分配
1 | const n = 123; // 给数值变量分配内存 |
数据类型与内存的关系
JS中分为两种数据类型
- 基础类型:Boolean,null,undefined,Number, String, Symbol,BigInt(ES6),
- 引用类型: Object,下列类型的顶层也是Object
- Array
- Set
- Map
- …
js的内存空间分为栈stack,堆heap
栈是遵循后入先出的一种数据结构,栈内元素只能通过列表的一端访问,这一端称为栈顶,为了得到栈底的元素,必须先依次拿掉栈顶的元素
在JS中,基本数据类型变量大小固定,操作简单,放入栈中存储,一般随着当前执行环境结束就会被销毁和回收。
堆是一种经过排序的树形数据结构,每个节点都有一个值。 通常我们所说的堆的数据结构,是指二叉堆。 堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。 由于堆的这个特性,常用来实现优先队列。堆的节点有顺序,但是想取其中的节点不需要像栈一样。
堆内存中的变量则不会,因为不确定其他的地方是不是还有一些对它的引用。 堆内存中的变量只有在所有对它的引用都结束的时候才会被回收
垃圾回收
在高级语言解释器嵌入了“垃圾回收器”,主要工作是跟踪内存分配与使用,在适当时机释放不再使用的内存。但是存在一些问题是,内存跟踪是一个近似的过程,因为要知道是否仍然需要某块内存是无法判断的。
引用计数垃圾收集
当声明一个引用类型并赋值给变量时,这个值的引用次数初始为1,每增加一次引用,计数 + 1,变量被其他值覆盖,计数 - 1,只有计数为0,才能被回收
1 | // 创建了两个对象,分别是obj和inner,inner又作为obj的属性 |
标记清除算法
算法分为两个阶段
Mark
- 运行时,讲内存中所有变量标记为0
- 从各个根对象遍历,将非垃圾变量标记为1
Sweep
- 标记为0的变量内存进行释放
1
2
3
4
5
6
7
8
9
10function fn(a){ // 开始执行此函数时,将其作用域中a、B以及匿名函数标记为0
alert(a) // 0
let B = new Object() // 0
return function (){ // 由于这里return出去会被其他变量引用,故标记变为1
altert(B) // 由于这里的闭包,B的标记变为1
}
... // 执行函数完毕,销毁作用域,在某个GC回收循环时会清理标记为0的变量a,B和匿名函数被保留了下来即非垃圾变量
}
let fn2 = fn(new Object())
// 补充一下:fn和fn2作为window.fn和window.fn2,标记一直为1,仅仅当手动设置fn=null和fn2=null才会标记为0标记整理
存在的问题是,内存释放后,未被释放的内存位置是不变的,会导致空闲内存不是连续的,造成内存碎片问题,所以会在标记结束后,讲不需要清除的对象移动至内存的一端,剩余部分进行清理