垃圾回收
浏览器中的内存管理过程,它负责释放不再需要的内存以及清理不再使用的对象,以确保浏览器的性能和稳定性。
chrome 垃圾回收
- 垃圾回收器(Garbage Collector):Chrome 使用垃圾回收器来自动检测和清理不再使用的内存。这些垃圾回收器会定期扫描浏览器的内存,标记不再需要的对象,然后释放它们占用的内存。
- 分代垃圾回收(Generational Garbage Collection):Chrome 使用分代垃圾回收策略。它将内存对象分为新生代和老生代两个部分。大多数对象在创建后都会被分配到新生代,而经过多次回收仍然存活的对象则会被提升到老生代。Chrome 会根据对象的生命周期选择不同的回收策略,以提高回收效率。
- 标记-清除算法(Mark and Sweep Algorithm):这是一种常见的垃圾回收算法,被用于检测和清理不再使用的对象。该算法分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会遍历内存中的对象,并标记出所有仍然活跃的对象。在清除阶段,垃圾回收器会释放未标记的对象所占用的内存。
- 空闲时回收(Idle Garbage Collection):Chrome 在浏览器空闲时执行垃圾回收操作,以减少对用户体验的影响。这意味着当用户暂停浏览网页或者系统空闲时,Chrome 会尽可能地执行垃圾回收操作。
- 内存泄漏检测(Memory Leak Detection):除了垃圾回收外,Chrome 还提供了内存泄漏检测工具,帮助开发人员发现和修复页面中的内存泄漏问题。这些工具可以识别出未释放的内存引用,从而帮助开发人员改进代码。
Javascript 垃圾回收机制
当内存不再占用,此时需要内存管理对不使用的内存区域进行清空操作,这就是所谓的垃圾回收机制。
垃圾回收策略
- 引用计数(Reference Counting):
- 这是一种简单的垃圾回收算法,它跟踪每个对象被引用的次数。
- 当对象被引用时,其引用计数加一;当对象被解除引用时,引用计数减一。
- 当对象的引用计数为零时,说明没有任何引用指向该对象,垃圾回收器会将其标记为可回收对象,并释放其占用的内存。
- 引用计数算法简单直观,但存在循环引用问题,即两个或多个对象相互引用,导致它们的引用计数永远不会为零,从而无法被回收。
限制:当两个对象出现循环引用的时候,此时,垃圾回收器就会判定该对象存在引用,不会对其进行回收。这就可能造成内存泄漏问题。
function circularReference() {
// 创建一个对象A
let objA = {a:1}
// 创建一个对象B
let objB = {b:2}
// objA.a 属性引用 objB 对象
objA.a = objB
//objB.b 属性引用 objA 对象
objB.b = objA
// 此时,objA 和 objB 就会出现循环引用
}
circularReference()
- 标记-清除算法(Mark and Sweep):
- 这是一种基于可达性的垃圾回收算法,它通过检测不再被引用的对象来进行垃圾回收。
- 垃圾回收器从根对象(通常是全局对象)开始,递归地遍历所有对象,并标记那些仍然可达(被引用)的对象。
- 在标记阶段结束后,垃圾回收器会遍历所有对象,清除未被标记的对象,释放其占用的内存。
- 标记-清除算法解决了引用计数算法的循环引用问题,但可能会产生内存碎片化,影响内存分配的效率。
- 标记-整理算法(Mark and Compact):
- 这是标记-清除算法的改进版,用于解决内存碎片化的问题。
- 在标记阶段结束后,垃圾回收器会将存活的对象向内存的一端移动,然后清理掉边界外的所有内存。
- 这样可以将所有存活的对象连续地放置在一起,从而避免了内存碎片化问题,提高了内存分配的效率。
- 增量式垃圾回收(Incremental Garbage Collection):
- 这是一种优化策略,将垃圾回收过程分解为多个小步骤,在执行 JavaScript 代码的同时逐步进行垃圾回收。
- 这样可以减少单次垃圾回收所带来的停顿时间,提高了 JavaScript 程序的响应性和性能。
v8 存储类型
- 堆(Heap):
- JavaScript对象和数据都存储在堆中。
- V8的堆是一个动态分配的内存区域,用于存储所有的JavaScript对象,包括函数、变量和对象实例。
- V8的垃圾收集器负责管理堆内存的分配和回收。
- 栈(Stack):
- V8使用栈来管理函数调用以及局部变量。
- 每当一个函数被调用时,V8都会为该函数创建一个新的栈帧,其中包含函数的参数、局部变量以及函数调用的上下文信息。
- 当函数执行完毕时,对应的栈帧会被销毁。
V8 引擎初始化内存空间,主要将堆分为以下几个区域:
- 新生代内存区
- 老生代内存区
- 大对象区
1.新生代
V8 引擎中的新生代堆是用于分配大部分 JavaScript 对象的内存区域。它主要用于存储生命周期较短的对象,因为这些对象通常的特点是被创建后很快就不再被引用,所以使用一种称为"Scavenger"的垃圾回收算法进行内存管理。新生代堆是一个比较小的内存区域,通常在几百KB到几MB之间。
新生代堆通常被划分为两个相等的空间,分别称为 "From 空间" 和 "To 空间"。
在开始时,所有的对象都会被分配到 From 空间中。当 From 空间被占满后,就会触发一次垃圾回收,这时候 V8 引擎会执行一次 Scavenger("清道夫") 算法的垃圾回收过程。
Scavenger 算法步骤
- 标记存活对象:从根对象(通常是全局对象和执行栈中的变量)出发,通过一种称为"追踪"的方式,标记所有能够被访问到的对象为存活对象。
- 复制存活对象:将所有存活的对象复制到 To 空间中。在复制的过程中,V8 引擎会尝试将所有存活对象紧凑地放置在 To 空间的起始位置。
- 清理非存活对象:清理 From 空间中所有未被复制到 To 空间的对象,这些对象被视为非存活对象,它们的内存空间可以被重新利用。
- From 与 To 空间交换:将 From 空间和 To 空间的角色进行交换,即将 To 空间变为新的 From 空间,同时将旧的 From 空间作为新的 To 空间。这样做的目的是为了下一次垃圾回收做好准备。
新生代堆的特点是,它的对象生命周期比较短暂,而且对象的存活率相对较低,所以适合使用复制算法来进行垃圾回收。不过,对于那些存活时间较长的对象,它们可能会被晋升到老生代堆中。
2.老生代
老生代堆是 V8 引擎中的另一个内存区域,用于存储长期存在且相对稳定的对象。与新生代堆相比,老生代堆的对象通常具有更长的生命周期,可能经历多次垃圾回收循环后仍然存活。老生代堆的大小通常比新生代堆大得多,通常在几十到几百兆字节之间,这取决于所使用的 V8 版本以及运行时环境的配置。
老生代堆使用不同于新生代堆的垃圾回收策略,因为老生代中的对象通常存活时间更长,而且可能会出现大量的引用关系交叉,导致简单的复制算法效率低下。因此,V8 引擎在老生代堆中采用了更复杂的垃圾回收算法来管理内存。
V8 引擎中常见的老生代垃圾回收算法包括:
- 标记-清除算法(Mark-Sweep)
- 这是最常用的老生代垃圾回收算法之一。它分为两个阶段:标记阶段和清除阶段。
- 在标记阶段,垃圾回收器会从根对象出发,标记所有能够被访问到的对象。
- 在清除阶段,垃圾回收器会遍历整个堆,清除未被标记的对象,从而释放它们占用的内存空间。
- 标记-整理算法(Mark-Compact)
- 在这种算法中,垃圾回收器会在标记阶段完成标记工作后,对存活的对象进行整理,将它们向堆的一端移动,从而释放出一段连续的内存空间。
- 这种算法的优点是可以避免内存碎片化,提高内存利用率。
- 增量式垃圾回收(Incremental Garbage Collection)
- 这是一种优化策略,将垃圾回收过程分解为多个小步骤,在执行 JavaScript 代码的同时逐步进行垃圾回收。
- 这样可以减少单次垃圾回收所带来的停顿时间,提高了 JavaScript 程序的响应性和性能。
老生代堆的垃圾回收通常比新生代堆的垃圾回收消耗更多的时间,因为它们的对象数量更多,而且垃圾回收算法更复杂。因此,在实际的应用中,需要注意尽量减少老生代对象的创建和销毁,以及合理地调整 V8 引擎的垃圾回收参数,以提高性能和内存利用率。
3.大对象区域
大对象区(Large Object Space)是 V8 引擎中的一部分,用于存储较大的对象。通常,大对象区主要用于存储那些大小超过新生代堆限制的对象,这些对象可能是大型数组、大型缓冲区或其他占用大量内存的数据结构。
由于大对象可能会占用较大的连续内存空间,因此 V8 引擎采用了特殊的内存分配和管理策略来处理这些对象。一般来说,大对象会直接被分配在大对象区中,而不会通过新生代堆的复制算法进行分配。这是因为大对象的复制可能会导致内存分配和复制操作的性能开销过大。
大对象区的垃圾回收通常也与新生代堆和老生代堆的垃圾回收算法有所不同。对于大对象区,可能采用更简单的垃圾回收策略,例如标记-清除算法,以及对大对象的内存分配和回收进行更加精细化的管理。