声明
一般来说像C语言或者Java这种底层语言一般都有底层的内存管理接口,比如 malloc()
和free()
。相反,JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让 JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。
这些也属于在Javascript创建之初,遗留的问题
内存的生命周期
不管是什么类型的语言,在生命周期这一部分都是基本相似的
- 分配你所需要的内存
- 使用分配到的内存(读、写)
- 不需要时将其释放\归还
所有语言第二部分都是明确的。第一和第三部分在底层语言中是明确的,但在像 JavaScript 这些高级语言中,大部分都是隐含的。所以这一块的分配和归还容易被忽视
Javascript的内存分配
值的初始化
为了不让程序员费心分配内存,JavaScript 在定义变量时就完成了内存分配。
var n = 123; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存
var o = {
a: 1,
b: null,
}; // 给对象及其包含的值分配内存
// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"];
function f(a) {
return a + 2;
} // 给函数(可调用的对象)分配内存
在JavaScript中基础数据类型都是存储在栈内存空间当中
引用数据类型,实际是存储在堆内存当中,栈内存中一般存放的是其地址指针
通过函数调用分配内存
有些函数调用结果是分配对象内存:
var d = new Date(); // 分配一个 Date 对象
var e = document.createElement("div"); // 分配一个 DOM 元素
有些方法分配新变量或者新对象:
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一个新的字符串
// 因为字符串是不变量,
// JavaScript 可能决定不分配内存,
// 只是存储了 [0-3] 的范围。
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新数组有四个元素,是 a 连接 a2 的结果
使用值
使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
当内存不在需要使用时释放
大多数内存管理的问题都在这个阶段。在这里最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”。它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它。
高级语言解释器嵌入了“垃圾回收器”,它的主要工作是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。这只能是一个近似的过程,因为要知道是否仍然需要某块内存是无法判定的(无法通过某种算法解决)。
垃圾回收机制
垃圾回收机制的目的
清除不再使用的对象,腾出所占用的内存
垃圾回收
当定义一个变量,我们给他重新赋值一个新的对象后,原来的对象就不再使用了,我们就可以把他清除掉
方法
1.引用计数法
引用计数法的逻辑非常简单,就是当对象被引用一次,引用次数就+1;取消引用一次,引用次数就--1;当引用次数为0时,就会被回收机制回收。
上面是我们理论上的解释,但是实际上,在进行循环引用时,引用次数绝对不可能为0,这将导致无法回收,内存无法释放。
2.标记计数法
标记计数法是因为在循环引用中,引用计数法无法做到标准的判定,所以诞生的。
有俩个关键点分别是:标记和清除;
首先我们将所有的对象标记为0,在进行一次对象遍历后,将所有存活的对象标记为1,将所有标记为0的对象清除,如何将标记为1的对象再次标记为0,方便下一次处理
但是这样回收之后,我们发现在内存空间当中会导致很多地方空缺,让存储的数据不连续,将来如果来一个新的对象给他分配储存空间将成为一个很大的问题
现在一般对于新的对象分配存储空间一般有三个方案
- First - fit : 找到能放下新对象的第一块位置
- Best - fit : 找到能放下新对象的最小的一块位置
- Worst - fot :找到最大的一块位置,将这一块直接给新对象
第一种方法:性能最好,是现在主流的方法
第二种方法:最节省空间,但是影响性能
第三种方法:这样会造成更多的不连续的空间