JS引擎
V8引擎是Chrome开发的高性能的JS引擎;采用JIT即时编码技术极大提高了代码的执行速度;内存管理方面使用了分代式垃圾回收机制来有效管理内存,这使得他在性能和效率上表现出色;
JS引擎运行的过程:
解析、编译、执行
JS引擎主要功能:
- 语法解析:检查代码是否符合js语法规则, 不符合抛出错误
- 作用域管理:创建和管理变量作用域,确定变量的访问范围和生命周期
- 内存管理:垃圾自动回收机制, 回收不再使用的对象所占的内存空间
- 对象管理:创建、操作和销毁对象
什么是垃圾回收
JS 有自动垃圾回收机制,垃圾回收器会跟踪对象,定期找出不被引用的对象或变量, 释放其所占的内存;最常用的有标记清除和引用计数两种算法
现代 JavaScript 引擎(如 V8引擎)通常会综合使用这些算法,根据不同的场景和对象特性选择最合适的垃圾回收策略,以达到性能和内存管理的平衡。
- V8引擎的垃圾回收机制 是 js垃圾回收机制在V8这个特定环境下的实现;
- js语言规范规定了内存自动管理的理念
- v8将理念化为实际的算法和策略
1. 引用计数:
定义:
引用计数是用于内存管理的算法;通过跟踪对象的引用数量来决定是否回收内存。
核心思想:
当一个对象被引用时,他的引用计数+1;当引用移除时候;引用计数-1;当引用计数为0;则表示可以被回收了
缺点
循环引用的问题:如果ab互相引用,那么计数永远不能为0,存在内存泄漏的问题- 性能开销:频繁的引用计数更新会带来一定的性能开销,特别是在对象引用频繁变化的情况下。
优点:
- 算法
简单 实时性比标记清除高, 因为标记清除需要等待垃圾回收器的周期执行;而对象的引用计数为0时,可立即回收内存
解决:
虽然引用计数算法简单,但由于其循环引用的问题,现代 JavaScript 引擎(如 V8)并不单纯使用引用计数算法,而是结合标记 - 清除等算法;标记 - 清除算法可以解决循环引用的问题,它通过标记可达对象,清除不可达对象,在一定周期内执行,与引用计数算法互补,共同实现更高效的垃圾回收。
2. 标记清除
工作原理:
- 遍历-标记-可达
- 遍历-未标记可达的 - 清除
从根对象开始,
遍历所有可访问的对象,并将他们标记为可达,这个过程通常使用深度优先搜索或广度优先搜索;
遍历堆中所有对象,将
未标记为可达的对象视为垃圾, 进行回收
优点:
可以解决循环引用问题:因为只要对象不可达(不被根对象引用),即使存在循环引用, 也会被标记为垃圾
缺点:
可能会产生内存碎片:因为回收的对象可能是不连续的,导致后续需要大内存的时候,无法分配;
其他垃圾回收机制:
标记整理:解决内存碎片化,但移动对象会带来额外开销- 标记复制:
- 分代收集:
- 原理:新生代(标记复制) + 老生代(标记清除或者标记整理)
- 优点:不同代对象采用不同的回收策略,
提高回收效率 - 缺点:需要
更多内存空间来管理不同代的对象和复制操作
- 增量收集:
- 工作原理:将垃圾回收任务
分为多个小步骤,分阶段执行,避免长时间的垃圾回收暂停,提高响应能力, - 缺点:但实现复杂,需要
更多的状态管理和上下文切换,可能会影响垃圾回收的总体性能; - 优点:减少垃圾回收导致的应用程序暂停
- 工作原理:将垃圾回收任务
- 并发收集:
- 工作原理:垃圾回收器和应用程序并发执行,在应用程序运行的同时进行部分垃圾回收工作。
- 优点:最小化 垃圾回收对应用程序的影响
- 缺点:实现复杂,
需要解决并发访问内存的同步问题
基于对象的生命周期将堆内存划分为不同的代,一般分为新生代和老生代。
常见的内存泄漏的方式:
- 闭包
- 意外的全局变量
- 事件监听没有移除
优化内存管理的方式:
在开发 JavaScript 应用时,虽然通常不需要直接操作垃圾回收机制,但了解这些算法可以帮助你更好地管理内存,避免内存泄漏和性能问题。例如,避免不必要的全局变量和闭包,及时释放不再使用的对象引用,以减轻垃圾回收的负担。
- 减少全局变量:如使用闭包封装或局部变量,因为全局变量存活时间长 不容易被垃圾回收
- 避免循环引用:可以使用WeakMap 弱引用
- 手动解除不再使用的对象引用:设置为null
- 局部作用域控制:减小作用域范围,可以更快释放
总结:
JS 有垃圾自动回收机制,常用的有引用计数 和标记清除两种算法,引用计数有循环引用的问题,所以 现代js引擎通常会结合其他算法来解决引用计数的不足,以实现可靠的垃圾回收机制; 除了引用计数,还有标记清除(有内存碎片的问题),标记整理(有移动开销的问题),标记复制,分代管理(实现复杂, 需要管理不同代对象和复制问题),增量收集(分段执行,较少暂停时间),并发收集(考虑并发问题)