浏览器 V8 引擎 GC 算法原理

内存管理#

内存:可读写的单元组成,表示一片可操作的空间 管理流程: 申请-使用-释放

function fn() {
arrlist = [];
arrlist[10000] = "hello world";
}
fn();
// 申请
let obj = {};
// 使用
obj.name = "ll";
// 释放
obj = null;

js 中的垃圾回收#

js 的内存管理是自动的 对象不再被引用时是垃圾 对象不能从根上访问到时是垃圾(可达对象,引用。作用域链) 根可理解为全局

let obj = { name: "ming" };
let aa = obj;
obj = null;

常见 GC 算法#

GC 就是垃圾回收机制的简写 GC 可以找到内存中的垃圾。并释放和回收空间

GC 算法是什么 GC 是一种机制,垃圾回收完成的具体工作 工作的内容就是查找垃圾释放空间/回收空间 算法就是工作时查找和回收所遵循的规则

常见 GC 算法

  • 引用计数
  • 标记清除
  • 标记整理
  • 分代回收

引用计数实现原理#

核心思想:设置引用数。判断当前引用数是否为 0 引用关系改变时修改引用数字 引用数字为 0 立即回收

const user1 = { age: 1 };
// user1被引用着不会被回收
const nameList = [user1.age];

优点: 发现垃圾立即回收 最大限度减少程序暂停 缺点: 无法回收循环引用的对象 时间开销大

// 对象之间循环引用
function fn() {
const obj1 = {};
const obj2 = {};
obj1.name = obj2;
obj2.name = obj1;
}
fn();

标记删除算法实现原理(V8 使用)#

核心思想: 分标记和清除二个阶段完成 一阶段:遍历所有可达对象找标记活动对象(进行递归查找) 二阶段:遍历所有对象清除没有标记对象 回收相应的空间

优缺点: 优点:解决循环引用的删除操作 缺点:空间碎片化

标记整理算法实现原理#

标记阶段会先进行整理,释放内存连续,相对于标记清除内存是连续的

常见 GC 算法总结#

  • 引用计数 使用引用计数器,可以即使回收垃圾对象,减少程序卡顿,无法回收循环引用的对象,资源消耗大
  • 标记清除,容易产生碎片化空间,不会立即回收垃圾对象
  • 标记整理,可以解决空间碎片化,不能立即回收垃圾对象

认识 V8#

主流执行引擎 V8 采用即时编译 v8 内存设限 64 位 1.5G 32 位 800M

V8 垃圾回收策略#

内存设有上限 采用分代回收思想 内存分为新生代/老生代 对象 常用的 GC 算法

  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量

V8 是如何回收新生代对象#

  • v8 内部把空间分为两部分

  • 小空间用于存储新生代对象(32M/16M)

  • 新生代指的是存活时间较短的对象 回收实现策略:

  • 回收采用复制算法和标记整理

  • 新生代内存分为两个等大小空间

  • 使用空间为 From,空闲空间为 To

  • 活动对象存储在 From 空间

  • 标记整理后将活动对象拷贝至 To

  • From 与 To 交换空间完成释放 注意: 拷贝过程中出现晋升 晋升就是将新生对象移动到老生代 一轮 GC 还存活的新生代需要晋升 To 空间使用率超过 25%

    V8 回收老生代对象#

    存放右侧老生代区域 64 位系统 1.4G,32 位 700M 老生代对象就是存活时间较长的对象 主要采用标记清除/标记整理/增量标记 首先使用标记清除垃圾完成空间回收 采用标记整理进行空间优化(晋升时空间不足触发)

    细节对比 新生代 空间换时间 老年代 数据多不适合复制算法 增量标记

    V8 回收总结#

    为浏览器设置的,内存设大的化 回收时间会变长 目前最大为 1 秒

    Performance 工具#

    目的是内存空间的良性循环 提供监控方式

    内存问题的体现#

    • 页面出现延迟加载或者经常性暂停
    • 出现持续性糟糕的性能
    • 页面的性能随时间延长越来越差

    内存监控的几种方式#

    内存泄漏: 内存使用持续升高 内存膨胀:在多数设备上存在性能问题 频繁垃圾回收:通过内存变化图分析

    • 浏览器任务管理器

    • Timeline 时序图记录

    • 堆快照找分离 dom

    • 判断是否存在频繁垃圾回收

代码优化#

Jsperf 使用

慎用全局变量#

全局变量定义在全局执行上下文,是所有作用域链的顶端

缓存全局变量#

例如 document

通过原型对象添加方法#

避开闭包陷阱#

for 循环优化#

forEach > for 循环 > for in

直接量替换 object#

通过字面量创建对象

// bad
const arr = new Array();
arr[0] = 1;
arr[1] = 2;
// good
const arr1 = [1, 2];