JavaScript 内存管理
内存管理
- 内存分配:JavaScript 中的内存分配通常是由 执行环境 自动处理的,开发者无需手动分配内存。内存分配的关键在于变量和对象的创建。
- 内存使用:变量的读写就是内存使用
- 内存释放:JavaScript 中的内存回收通过 垃圾回收机制(Garbage Collection) 来自动管理。垃圾回收的主要任务是识别不再需要的内存并释放它,从而避免内存泄漏。
垃圾回收
垃圾回收(Garbage Collection,缩写为 GC)即内存回收,是一种自动的存储器管理机制。 当程序占用一部分内存不再被程序访问称为内存垃圾,JavaScript 引擎会自动释放这部分内存即垃圾回收。
- 对象不再被引用时成为内存垃圾
- 对象不能从根上访问到时成为内存垃圾
JavaScript 可达对象
可达对象指从根上出发可以访问(通过引用或者作用域链)到的对象,在 JavaScript 中根可以理解为全局变量对象或全局执行上下文(window 或 global)
function objLink(obj1, obj2) {
obj1.next = obj2;
obj2.prev = obj1;
return {
o1: obj1,
o2: obj2,
}
}
const obj = objLink({a: 1}, {b: 2});
// 删除obj.o1的全部引用,使obj.o1变成 不可达对象 对应的内存空间会被回收
obj.o1 = null;
obj.o2.prev = null;
GC 回收机制
引用计数算法
引用计数算法是最早的也是最简单的垃圾回收实现方法,这种方法为占用物理空间的对象附加一个计数器。 当有其他对象引用这个对象时计数器加一,反之引用解除时减一。垃圾回收器会定期检查,计数为零的对象会被释放。 缺点是:无法回收循环引用的存储对象,因此现代 JavaScript 引擎主要使用标记-清除算法
标记清除算法
垃圾回收器会标记所有仍在使用的对象,然后清除未标记的对象
标记清除算法分为两个阶段:
- 标记阶段:定时从根部(window)对象扫描对象,凡是能够到达的对象都是还需要继续使用的,并将这些对象标记为“活动对象”
- 清除阶段:
- 垃圾回收器遍历整个堆内存,找到所有未被标记的对象。
- 将这些未被标记的对象从内存中清除,释放它们占用的空间。
- 最后,清除所有对象的标记,为下一次垃圾回收做准备。
缺点:
- 内存碎片化:清除阶段会释放不连续的内存块,可能导致内存碎片化,降低内存使用效率。
- 暂停时间(Stop-the-World):在标记和清除过程中,JavaScript 引擎需要暂停执行代码,这可能导致性能问题,尤其是在内存较大的应用中。
标记整理
标记整理算法的增强版本,解决标记清除算法的空间碎片化问题
分代回收
V8 引擎 GC 算法
内存泄露
不在使用的内存没有及时释放就是内存泄漏,内存泄漏容易导致内存溢出使程序崩溃。以下情况会发生内存泄漏:
- 意外全局变量:解决方案使用严格模式
- 在非严格模式下当引用未声明的变量时,会在全局对象中创建一个新变量
- 使用
this创建变量
- 未被清理的定时器
setInterval,执行过程中会不断地占用内存 - 未被清理的事件监听
DOM引用:DOM元素被移除后,JS 变量引用会导致无法进行内存回收,DOM的变量在使用后可以赋值为null- 闭包
- 不合理的数据结构使用
arr[1000] = 1VSobj[1000] = 1
```JS 全局变量案例 function fn () { a = "Actually, I'm a global variable" } function fn () { this.a = "Actually, I'm a global variable" } fn();
```JS 未清理定时器案例
var serverData = loadData();
setInterval(function() {
var root = document.getElementById('root');
if(root) {
root.innerHTML = JSON.stringify(serverData);
}
}, 5000); // 每 5 秒调用一次
以上代码问题:如果root元素被移除后,定时器实际没有任何作用,但是定时器没有被回收导致定时器的回调函数以及回调函数内部的变量引用(本例中的 serverData)也无法被回收。
TODO:内存泄漏检测
- 开发工具: