前端学习20 垃圾回收机制
在前端中,内存管理是一个非常重要的领域。浏览器的垃圾回收机制(Garbage Collection ,GC)是现在浏览器中的核心的部分之一,它可以自动管理内存的分配与回收,帮助开发者避免手动管理内存,从而减少内存泄露和性能问题的风险。
1.垃圾回收机制
垃圾回收是一种自动内存管理机制,用于识别和释放不再使用的内存,垃圾回收的核心目标是释放不在使用的内存空间。在浏览器中,当页面中的某些对象不在被需要时,垃圾回收机制会自动将他们从内存中移除,确保不会发生内存泄露。
JavaScript是一种内存自动管理的语言,内存管理的核心就是垃圾回收。
它主要通过两种方式来检测和释放内存:
- 引用计数(Reference Couning):追踪每个对象的引用次数,当一个对象的引用次数为零,就认为该对象不再使用,可以释放它占用的内存。
- 标记清除(Mark-and-Sweep):通过标记所有可达的对象,然后清除所有玮柏哦记得对象来回收内存。
引用计数曾经是早期垃圾回收机制中的一个重要方法,但由于其无法解决循环引用、性能开销较大以及不能有效解决内存碎片问题,现代浏览器和大多数编程语言已经转向使用更高效的垃圾回收策略,如 标记-清除算法、分代回收、增量回收 和 并行回收 等。
2.垃圾回收机制的工作原理
2.1 引用计数
引用计数(Reference Counting)是一种较为基础的垃圾回收算法,它通过追踪对象的引用次数来判断对象是否可以被回收。每个对象都维护一个计数器,表示它被多少个其他对象引用。当一个对象的引用计数降为零时,表示没有任何引用指向该对象,可以安全地回收该对象的内存。
工作原理:
- 初始化引用次数:每当一个对象被创建并将其引用赋值给一个变量时,该对象的引用计数为1。
- 对象引用增加与减少:当一个对象被引用时(如赋值给其他变量或作为函数参数传递),它的引用计数增加;当一个引用被销毁时(如局部变量超出作用域或赋值为null),该对象的引用计数减少。
- 回收垃圾对象:当一个对象的引用计数降到零时,意味着没有任何引用指向该对象,垃圾回收器会回收该对象占用的内存。
let obj1 = { name: "Object 1" }; // 引用计数为 1 let obj2 = obj1; // 引用计数为 2,因为 obj2 引用了 obj1 let obj3 = { ref: obj1 }; // 引用计数为 3,因为 obj3 引用了 obj1 obj2 = null; // 引用计数为 2,obj1 的引用计数减少 1 obj3 = null; // 引用计数为 1,obj1 的引用计数再次减少 1 obj1 = null;// 此时,obj1 的引用计数变为 0,垃圾回收器可以回收 obj1
在上述代码中,obj1 的初始引用计数为 1,后来通过 obj2 和 obj3 增加了引用计数。当 obj2 和 obj3,obj1 被设置为 null 时,obj1 的引用计数逐渐减为 0,垃圾回收器可以回收 obj1 占用的内存。
但是引用计数会存在一定问题:
- 循环引用问题
引用计数的一个显著缺点是无法解决循环引用问题。例如,如果两个对象互相引用对方,它们的引用计数始终大于 0,即使它们不再被其他对象引用,垃圾回收器也无法将它们回收,从而导致内存泄漏。
2.2 标记清除
标记清除是现代垃圾回收器中常用的内存回收算法,广泛引用与JavaScript、Java、Python等编程与原的垃圾回收机制。它的核心思想是通过标记存活对象,并清除未标记的对象来实现内存的回收。
标记-清除算法的基本流程可以分为两个阶段:标记阶段(Mark Phase)和清除阶段(Sweep Phase)。
- 标记阶段
在这个阶段,垃圾回收器会从根对象(Root Object)开始遍历,标记所有可以访问到的对象为“活跃的”。
根对象包括全局对象当前执行栈上的局部变量,活动函数等。
遍历所有可达的对象,并将这些对象标记为“活动”状态,意味着它们仍然被程序所引用。
- 清除阶段
在标记阶段完成后,垃圾回收器会见检查堆中的所有对象。
所有没有被标记为活动的对象,(即不再被任何其他对象引用的对象)被认为是垃圾,可以回收并释放内存。
这是,垃圾回收器会删除这些不在需要的对象,释放他们占用的内存空间。
function createObjects() { let obj1 = { name: 'Object 1' }; let obj2 = { name: 'Object 2' }; let obj3 = { name: 'Object 3' }; obj1.ref = obj2; // obj1 引用 obj2 obj2.ref = obj3; // obj2 引用 obj3 // 假设程序中不再使用 obj1 和 obj3 obj1 = null; // 断开 obj1 和 obj2 的引用 obj2 = null; // 断开 obj2 和 obj3 的引用 } createObjects();
在上述代码中,obj1、obj2 和 obj3 都是对象,它们之间通过引用相互连接。在 createObjects 函数执行完后,obj1 和 obj2 都被设置为 null,它们之间的引用被断开。
2.3 分代回收
分代回收基于一个观察:大多数对象的生命周期很短,只有少数对象会存活较长时间。因此,垃圾回收器将内存分为不同的代(Generation),并对不同代采用不同的回收策略。
- 新生代(Young Generation):存放新创建的对象。新生代的垃圾回收频率较高,采用复制算法(Copying Algorithm)进行回收。
- 老生代(Old Generation):存放存活时间较长的对象。老生代的垃圾回收频率较低,采用标记-清除或标记-整理(Mark-and-Compact)算法进行回收。
生成垃圾回收算法基于这样一个假设:大部分对象会很快变得不可达,因此,年轻代的对象会频繁进行垃圾回收。只有生命周期较长的对象才会进入老年代,老年代的回收相对较少,避免频繁回收带来的性能损耗。