V8内存管理
- 程序运行时需要分配内存
- 分为堆内存和栈内存
栈内存
- 存放基本类型和引用类型的指针
- 空间是连续的,增加删除只需要移动指针,速度快
- 空间是有限的,满了会报错
- 栈一般是在函数执行时创建的,函数执行结束,栈销毁
堆内存
- 内存空间不连续,空间较大
- 主要存储JS中引用类型
堆内存分类
新生代
- 生命周期较短的对象
老生代
- 存放生命周期较长的对象
- 新生代的对象经过两个周期的垃圾回收后,如果数据还在新生代中,则将它们放到老生代里
- 分为指针对象,和数据对象
Code Space
- 用于存放JIT已编译的代码
- 唯一有执行权限的内存
Large Object Space
- 专门存大对象
- 不会被垃圾回收
Map Space
- 存放对象的Map信息,即隐藏类
- 隐藏类是为了提升对象属性的访问速度
- V8为每个对象创建一个隐藏类,记录了对象的布局,包括所有的属性和偏移量
垃圾回收
- 垃圾:程序结束后,不再使用的数据
新生代
- 新生代有两个区域,数据区域From和空闲区域To
- 通过广度优先遍历,从根对象出发,把能遍历到的对象,从From复制到To区域,From和To角色互换
- 优点:不会出现内存碎片,缺点:浪费空间
- 新生代的GC比较频繁
- 新生代的对象晋升到老生代的条件:
- 经过一次GC还活着的对象
- 对象复制到To区域时,To区域的空间达到一定的限制
老生代
- 老生代里的对象,有些是从新生代晋升过来的,有些是比较大的对象直接分配到老生代里的,空间大、存活时间长
- 如果采用新生代的算法,浪费空间
- 采用标记清除和标记整理
标记清除
- 标记:垃圾回收之前,把所有对象设置成白色,从GC的根节点开始,通过深度优先遍历的方式,把遍历到的对象标记成黑色。黑色是或者的对象,白色是要清除的对象
- 清除:清除掉白的的对象
- 内存不连续,有内存碎片
标记整理
- 为了解决标记清除带来的内存碎片的问题
- 在整理过程中,把活着的对象向内存区的一端移动,移动完成直接清理边界外的内存
- 效率低,不会产生内存碎片。10次标记清除伴随一次标记整理
优化
- 在垃圾回收阶段,JS脚步需要暂停,全停顿(Stop the world)
- 如果时间过长,会引起卡顿
- 性能优化
- 把大任务拆成小任务,分步执行
- 把任务放在后台执行,不占用主线程
JavaScript执行 垃圾标记、垃圾清理、垃圾整理 JavaScript执行
------------ -------------->
Parallel(并行)
- 新生代的垃圾回收采用并行的策略,开启多个辅助线程进行垃圾回收
- 并行执行的时,也是全停顿,主线程不能进行任何操作,只能等待辅助线程结束
---辅助线程-->
---辅助线程-->
---辅助线程-->
------ ------>
增量标记
- 老生代的对象又多又大,采用增量标记的方法进行优化
- 把标记分为多个阶段,每个阶段只标记一部分,和主线程的执行穿插进行
- 为了支持增量标记,垃圾回收需要支持暂停和恢复,采用
黑白灰的标记方式- 黑:这个节点被GC根节点引用到,该节点和子节点都被标记完成
- 灰:这个节点被GC根节点引用到,但是子节点没有被标记完成,表示正在处理中
- 白:未被引用到,需要清理
- 如果有灰节点,下次从灰节点恢复标记
----开始标记---增量标记---增量标记---清理--整理--->
Write-barrier写屏障
- 当黑色指向白色节点时,会触发写屏障,会把白色节点设置为灰色
惰性清理
- 如果内存够用,先不清理,等JS代码执行完再清理
并发回收
- 增量标记和惰性清理没有减少暂停的时间
- 并发回收就是主线程在执行过程中,辅助线程在后台完成垃圾回收工作
- 标记全部有辅助线程处理,清理由辅助线程和主线程配合完成
---辅助线程标记--> ---清理整理-->
---辅助线程标记--> ---清理整理-->
------执行JS-->------------清理整理-->----->
并发和并行
- 都是同时执行多个任务
- 并行:同一时刻多个进程在运行
- 并发:进行上下文快速的切换