javascript中的内存管理

w3c中javascript的内存管理

内存生命周期

大部分语言的内存生命周期都是基本一致的:

  1. 分配所需的内存
  2. 使用内存,读写内存
  3. 不需要时释放内存

而在javascript中,不需要手动进行内存分配,会在定义变量的时候就完成了内存的分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const n = 123; // 给数值变量分配内存
const s = "some str"; // 给字符串分配内存

const obj = {
a: 1,
b: null
}; // 给对象及其包含的值分配内存

// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"];

function f(a){
return a + 2;
} // 给函数(可调用的对象)分配内存

// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue';
}, false);

数据类型与内存的关系

JS中分为两种数据类型

  • 基础类型:Boolean,null,undefined,Number, String, Symbol,BigInt(ES6),
  • 引用类型: Object,下列类型的顶层也是Object
    • Array
    • Set
    • Map

js的内存空间分为栈stack,堆heap

栈是遵循后入先出的一种数据结构,栈内元素只能通过列表的一端访问,这一端称为栈顶,为了得到栈底的元素,必须先依次拿掉栈顶的元素

在JS中,基本数据类型变量大小固定,操作简单,放入栈中存储,一般随着当前执行环境结束就会被销毁和回收。

堆是一种经过排序的树形数据结构,每个节点都有一个值。 通常我们所说的堆的数据结构,是指二叉堆。 堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。 由于堆的这个特性,常用来实现优先队列。堆的节点有顺序,但是想取其中的节点不需要像栈一样。

堆内存中的变量则不会,因为不确定其他的地方是不是还有一些对它的引用。 堆内存中的变量只有在所有对它的引用都结束的时候才会被回收

垃圾回收

在高级语言解释器嵌入了“垃圾回收器”,主要工作是跟踪内存分配与使用,在适当时机释放不再使用的内存。但是存在一些问题是,内存跟踪是一个近似的过程,因为要知道是否仍然需要某块内存是无法判断的。

引用计数垃圾收集

当声明一个引用类型并赋值给变量时,这个值的引用次数初始为1,每增加一次引用,计数 + 1,变量被其他值覆盖,计数 - 1,只有计数为0,才能被回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建了两个对象,分别是obj和inner,inner又作为obj的属性
// 这样两个对象都不可回收
let obj = {
inner: {
some: 1
}
};

const obj2 = obj; // obj2 引用原始obj指向的堆内存地址

obj = 'some test'; // 这时候obj2还是引用着原始obj执行的堆内存地址,而obj的值从原来的堆内存地址变成了基本类型字符串

obj = null; // 这时候obj可以释放,但是obj2指向的堆内存数据没有释放

obj2 = 'some test 1'; // obj2 的值从指向堆内存的地址变成了基本类型,这时候堆内存中的数据没有引用了,可以回收了

标记清除算法

算法分为两个阶段

  • Mark

    • 运行时,讲内存中所有变量标记为0
    • 从各个根对象遍历,将非垃圾变量标记为1
  • Sweep

    • 标记为0的变量内存进行释放
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function 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
  • 标记整理
    存在的问题是,内存释放后,未被释放的内存位置是不变的,会导致空闲内存不是连续的,造成内存碎片问题,所以会在标记结束后,讲不需要清除的对象移动至内存的一端,剩余部分进行清理

Author: ACE0220
Link: https://ace0220.github.io/javascript/javascript-memory/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.