敲开内存管理以及垃圾回收的大门
2021-03-26 本文已影响0人
贰玖是只猫
作为一个前端同学我们跨过了只关注实现业务功能的阶段后,会进入一个瓶颈。在这个瓶颈阶段,我们需要通过更多的知识实践来积累,让我们跨过这段平台期。前端架构、框架原理、性能优化等都是我们需要取掌握的技能。本章节就从性能优化的一个角度----JavaScript性能优化 进化吧!
内存管理
如果我们在日常的编程中不注意管理内存,会导致内存泄露, 产生浏览器崩溃等情况。
内存管理: 开发者主动申请、使用、释放内存空间
//申请
let obj = {}
//使用
obj.name = "zicoo"
//释放
obj = null
JavaScript 中的垃圾回收
- JavaScript中内存管理是自动的
- 对象不再被引用时是垃圾
- 对象不能从根上访问到时是垃圾
可达对象
- 可以访问到的对象就是可达对象(引用、作用域链)
- 可达的标准是从根触发是否能够被找到
- javascirpt 中的根可以理解为全局变量对象
引用与可达
let obj = {name: "sam"}
let obj1 = obj
obj = null //obj 仍然可达
GC - 垃圾回收机制
- GC是一种机制,垃圾回收器完成具体的工作
- 工作的内容是查找垃圾释放空间、回收空间
- 算法就是工作中查找和回收所遵循的规则
常见GC算法
- 引用计数
- 标记清除
- 标记整理
- 分代回收
引用计数
核心思想: 设置引用计数器,判断当前引用数为否为0
优点:
- 发现垃圾立即回收
- 最大限度减少程序暂停
缺点:
- 无法回收循环引用的对象
- 时间开销大
function fn() {
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1 //obj1 obj2 循环引用 无法将引用计数器清0
}
fn()
标记清除
核心思想: 分标记和清除两个阶段
- 遍历所有对象找到标记活动对象
- 遍历所有对象清除没有标记的对象
- 回收相应的空间
执行时间点: 当有效内存空间被耗尽的时候,会停止程序执行标记清除
标记活动对象依旧是从根出发,递归的方式遍历从根能到达的所有节点,并且标记为活动对象, 而未标记活动对象的节点则就是待回收节点
优点:
- 对于循环引用的对象可以被回收,可以弥补引用计数的缺点
缺点:
- 由于内存空间申请的时候是有序申请,但是回收的时候节省的内存空间信息是不会整合的,导致节省的内存空间不连续,从而造成空间碎片化
标记整理
- 标记整理算法是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段会先执行整理,移动对象位置, 使时间碎片连续
V8引擎
- 主流的JavaScript执行引擎
- V8采用即时编译
- V8内存设限 (64位系统不超过 1.5G 21位系统不超过800M)
V8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代、老声带
- 针对不同的对象采用不同算法
V8常见GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8 新生代对象回收
- V8 内存空间一分为二
- 小空间用于存储新生代对象(64位32M | 32位16M)
- 新生代对象指存活时间较短的对象
例如:局部作用域的变量是新生代, 全局变量存是老年代
新生代对象回收实现
- 回收过程采用复制算法 + 标记整理
- 新生代内存区分为两个等大空间
- 使用空间为From 状态, 空闲空间为 To状态
- 活动对象存于From 空间
- 等到回收阶段,标记整理后会将活动对象拷贝到To
- 释放掉From
细节说明 - 拷贝过程中可能出现晋升
- 晋升就是将新生代对象移动至老年代
- 一轮GC还存活的新生代需要晋升
- To 空间使用率超过25%,之后拷贝的对象则会晋升
V8 老年代对象回收
- 老年代64位操作系统1.4G ,32位操作系统700m
- 老年代活动对象指存活时间较长的对象
老年代对象回收实现
- 主要采用标记清除、标记整理、增量标记算法
- 首先使用标记清除完成垃圾空间的回收(存在空间碎片)
- 采用标记整理进行空间优化(新生代晋升时,老年代区域不足以存放新生代对象)
- 采用增量标记进行效率优化
增量标记
程序执行与标记间隔执行,达到清理标准一起清理
细节对比
- 新生代区域垃圾回收使用空间换时间
- 老年代区域垃圾回收空间大,不适合复制算法