前面的话
不管什么程序语言, 内存生命周期基本是一致的: 首先, 分配需要的内存; 然后, 使用分配到的内存; 最后, 释放其内存而对于第三个步骤, 何时释放内存及释放哪些变量的内存, 则需要使用垃圾回收机制本文将详细介绍 javascript 中的内存管理和垃圾回收
分配内存
为了不让程序员费心分配内存, JavaScript 在定义变量时就完成了内存分配
- var n = 123; // 给数值变量分配内存
- var s = "azerty"; // 给字符串分配内存
- var o = {a: 1,b: null}; // 给对象及其包含的值分配内存
有些函数调用结果是分配对象内存
- var d = new Date(); // 分配一个 Date 对象
- var e = document.createElement('div'); // 分配一个 DOM 元素
有些方法分配新变量或者新对象
- var a = ["ouais ouais", "nan nan"];
- var a2 = ["generation", "nan nan"];
- var a3 = a.concat(a2);
- // 新数组有四个元素, 是 a 连接 a2 的结果
存储方式
因为原始值占据空间固定, 是简单的数据段, 为了便于提升变量查询速度, 将其存储在栈 (stack) 中
由于复杂值的大小会改变, 所以不能将其存放在栈中, 否则会降低变量查询速度, 因此其存储在堆 (heap) 中, 存储在变量处的值是一个指针, 指向存储对象的内存处
使用内存
使用值的过程实际上是对分配内存进行读取与写入的操作读取与写入可能是写入一个变量或者一个对象的属性值, 甚至传递函数的参数
- var a = 1;
- console.log(a);// 读取内存中的值
- a = 2; // 写入内存
释放内存
大多数内存管理的问题都在这个阶段在这里最艰难的任务是找到所分配的内存确实已经不再需要了
Javascript 内嵌了垃圾收集器, 用来跟踪内存的分配和使用, 以便当分配的内存不再使用时, 自动释放它垃圾收集器会按照固定的时间间隔, 或代码执行中预定的收集时间, 周期性地执行这一操作
局部变量只在函数执行的过程中存在而在这个过程中, 会为局部变量在栈 (或堆) 内存上分配相应的空间, 以便存储它们的值然后在函数中使用这些变量, 直到函数执行结束此时, 局部变量就没有存在的必要了因此可以释放它们的内存以供将来使用在这种情况下, 很容易判断变量是否还有存在的必要; 但并非所有情况下都这么容易就能得出结论
垃圾收集器必须跟踪哪个变量有用哪个变量无用, 对于不再有用的变量打上标记, 以备将来收回其所占用的内存用于标识无用变量的策略通常有标记清除和引用计数两种
引用计数
引用计数是最简单的垃圾收集算法此算法把对象是否不再需要简化定义为对象有没有其他对象引用到它如果没有引用指向该对象(零引用), 对象将被垃圾回收机制回收
下面代码中, 两个对象 ab 被创建, 一个作为另一个的属性被引用, 另一个被分配给变量 o
var o ={ a: {b:2}}
o2 引用了 o
var o2 = o;
这个对象的原始引用 o 被 o2 替换了
o = 1;
现在, 这个对象有两个引用了, 一个是 o2, 一个是 oa
var oa = o2.a;
最初的对象现在已经是零引用了, 然而它的属性 a 的对象还在被 oa 引用, 所以还不能回收
o2 = "yo";
a 属性的那个对象现在也是零引用了, 它可以被垃圾回收了
oa = null;
循环引用
Netscape Navigator3.0 是最早使用引用计数策略的浏览器, 但很快它就遇到了一个严重的问题循环引用
引用计数算法有个限制: 无法处理循环引用在下面的例子中, 两个对象被创建, 并互相引用, 形成了一个循环它们被调用之后不会离开函数作用域, 所以它们已经没有用了, 可以被回收了然而, 引用计数算法考虑到它们互相都有至少一次引用, 所以它们不会被回收
- function f(){
- var o = {};
- var o2 = {};
- o.a = o2; // o 引用 o2
- o2.a = o; // o2 引用 o
- return "azerty";
- }
- f();
IE 低版本
IE8 - 浏览器中, 有一部分对象并不是原生 javascript 对象, 例如, 其 BOM 和 DOM 中的对象就是使用 c++ 以 COM(component Object Model 组件对象模型)对象的形式实现, 而 COM 对象的垃圾回收机制采用的就是引用计数策略该方式常常造成对象被循环引用时内存发生泄漏
- function f(){
- var element = document.getElementById('some_element');
- var myObject = new Object();
- myObject.element = element;
- element.someObject = myObject;
- }
- fn()
这个例子在一个 DOM 元素 (element) 与一个原生 javascript 对象 (myObject) 之间创建了循环引用其中, 变量 myObject 有一个名为 element 的属性指向 element 对象, 而变量 element 也有一个属性名为 someObject 的属性指向 myObject 由于存在这个循环引用, 即使将例子中的 DOM 从页面中移除, 它也永远不会被回收
为了避免类似这样的循环引用, 最好是在不使用它们的时候手工断开原生 javascript 和 DOM 元素之间的连接
- myObject.element = null;
- element.someObject = null;
将变量设置为 null 意味着切断变量与它此前引用的值之间的连接当垃圾收集器下次运行时, 就会删除这些值并回收它们占用的内存
为了解决此问题, IE9 把 BOM 和 DOM 对象都转换成了真正的 javascript 对象
标记清除
javascript 中最常用的垃圾收集算法是标记清除(mark-and-sweep), 这个算法把对象是否不再需要简化定义为对象是否可以到达如果对象不可到达, 对象将被垃圾回收机制回收
大多数浏览器实现使用的都是标记清除式的垃圾收集策略, 只不过垃圾收集的时间互有不同
这个算法假定设置一个叫做根 (root) 的对象 (在 Javascript 里, 根是全局对象) 定期的, 垃圾回收器将从根开始, 找所有从根开始引用的对象, 然后找这些对象引用的对象从根开始, 垃圾回收器将找到所有可以到达的对象和所有不能到达的对象
该算法称为标记清除, 是因为分为标记 (mark) 和清除 (sweep) 两个阶段
在标记阶段, 垃圾回收器会从根对象开始遍历, 每一个可以从根对象访问到的对象都会被添加一个标识, 于是这个对象就被标识为可到达对象
在清除阶段, 垃圾回收器会对内存从头到尾进行线性遍历, 如果发现有对象没有被标识为可到达对象, 那么就将此对象占用的内存回收, 并且将原来标记为可到达对象的标识清除, 以便进行下一次垃圾回收操作
在标记阶段, 从根对象 1 可以访问到 B, 从 B 又可以访问到 E, 那么 B 和 E 都是可到达对象, 同样的道理, FGJ 和 K 都是可到达对象在回收阶段, 所有未标记为可到达的对象都会被垃圾回收器回收
循环引用
使用标记清除算法, 循环引用不再是问题, 上面的示例中, 函数调用返回之后, 两个对象从全局对象出发无法获取因此, 他们将会被垃圾回收器回收
性能问题
垃圾收集器是周期性运行的, 而且如果为变量分配的内存数量很可观, 那么回收工作量也是相当大的在这种情况下, 确定垃圾收集时间间隔是一个非常重要的问题
IE 的垃圾收集器是根据内存分配量运行的具体一点说, 就是 256 个变量, 4096 个对象 (或数组) 字面量和数组元素 (slot) 或者 64kb 的字符串达到上述任何一个临界值, 垃圾收集器就会运行
这种实现方式的问题在于, 如果一个脚本中包含那么多变量, 那么该脚本很可能会在其生命周期中一直保有那么多的变量而这样一来, 垃圾收集器就不得不频繁地运行结果, 由此引发的严重性能问题促使 IE7 重写了其垃圾收集例程
IE7 的 javascript 引擎的垃圾收集例程改变了工作方式: 触发垃圾收集的变量分配字面量和数组元素的临界值被调整为动态修正 IE7 中的各项临界值在初始时与 IE6 相等如果垃圾收集例程回收的内存分配量低于 15%, 则变量字面量和数组元素的临界值就会加倍如果例程回收了 85% 的内存分配量, 则将各种临界值重置回默认值这样, 极大地提升了 IE 在运行包含大量 javascript 的页面时的性能
事实上, 在有的浏览器中可以触发垃圾收集过程在 IE 中, 调用 window.CollectGarbage()方法会立即执行垃圾收集
优化内存占用
使用具备垃圾收集机制的 javascript 的主要问题在于: 分配给 web 浏览器的可用内存数量通常要比分配给桌面应用程序的少, 目的是防止运行 javascript 的网页耗尽全部系统内存而导致系统崩溃内存限制问题不仅会影响给变量分配内存, 同时还会影响调用栈以及在一个线程中能够同时执行的语句数量
因此, 确保占用最少的内存可以让页面获得更好的性能而优化内存占用的最佳方式是: 为执行中的代码只保存必要的数据一旦数据不再有用, 最好通过将其值设置为 null 来释放其引用, 这种做法叫解除引用 (dereferencing) 这一做法适用于大多数全局变量和全局对象的属性, 局部变量会在它们离开执行环境时自动被解除引用
- function createPerson(name){
- var localPerson = new Object();
- localPerson.name = name;
- return localPerson;
- }
- var globalPerson = createPerson('test');
- globalPerson = null;
不过, 要注意的是, 解除一个值的引用并不意味着自动回收该值所占用的内存解除引用的真正作用是让值脱离执行环境, 以便垃圾收集器下次运行时将其回收
来源: https://www.cnblogs.com/xiaohuochai/p/8528677.html