垃圾回收

JavaScript中的垃圾

  • JavaScript中内存管理是自动的
  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到时是垃圾

    JavaScript中的可达对象

  • 可以访问到的对象就是可达对象(引用,作用域链)

  • 可达的标准就是从根出发是否能够被找到
  • JavaScript中的根就可以理解为是全局变量对象

    GC定义与作用

  • GC就是垃圾回收机制的简写

  • GC可以找到内存中的垃圾,并释放和回收空间

    常见GC算法

  • 引用计数:当变量声明的时候设置引用数,当引用关系改变时就会通过引用计数器修改引用数字,当前引用数是否为0时就会被立即回收。

优点:1. 发现垃圾时立即回收。 2. 最大限度减少程序暂停
缺点:1. 无法回收循环引用的对象。 2.时间开销大(要去时刻监控对象的引用关系 )

  • 标记清除:遍历所有对象并将活动对象进行标记然后清除掉没有被标记的对象与活动对象的标记回收相应的空间。

优点:解决了引用计数算法的循环引用对象无法被回收问题
缺点:容易产生碎片化空间,无法最大程度的利用内存空间(标记清除的回收的空间是碎片化的不连续的,当新申请的空间大了或小了都没法正常使用)。不会立即回收垃圾对象

  • 标记整理:可以看做是标记清除的增强,它标记阶段的操作和标记清除一致,清除阶段会先整理,移动对象位置

优点: 减少碎片化空间
缺点:不能立即回收垃圾对象
因为非活动对象的内存空间大小不一都是一块一块的,所以标记清除回收的内存空间是碎片化的
image.png
使用标记整理
image.png

V8

什么是V8

  • 一款主流的JavaScript执行引擎
  • 速度快,V8采用即时编译
  • 有内存设限在64位操作系统中是1.5G,在32位操作系统中是800M

V8垃圾回收策略

  • 采用分代回收的思想
  • 内存分为新生代,老生代
  • 针对不同对象采用不同算法

image.png

V8中常用GC算法

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

    V8内存分配

  • V8内存空间一分为二

  • 小空间用于存储新生代对象(32M | 16M)
  • 大空间用于存储老生代对象(1.4G | 700M)
  • 新生代指的是存活时间较短的对象(局部作用域对象)
  • 老生代对象是指存活时间较长的对象(全局变量,闭包中的一些数据)

    新生代对象回收实现

    新生代的内存区被分为两个等大的区域 使用空间form 与 空闲空间to,触发GC回收过程会使用复制算法+标记整理,将form中的活动对象标记整理复制到to空间中然后清除form空间的内容,再将form跟to进行交换完成空间释放。 在一轮GC还存活的新生代对象需要移动到老生代,如果to空间的使用率超过25%也需要移动到老生代

    老生代对象回收实现

    首先使用标记清除完成垃圾空间回收,当我们想将新生代对象移向老生代区域时如果空间不足的情况下就会进行标记整理,再用增量标记进行效率优化

新老生代的垃圾回收实现对比

  • 新生代区域垃圾回收使用空间换时间
  • 老生代区域垃圾回收不适合复制算法,因为新生代空间小还一分为二所以复制消耗性能较少,而老生代空间大,如果一分为二就比较浪费而且复制消耗性能较大。

    界定内存问题的标准

  • 内存泄露:内存使用持续升高没有回落。

  • 内存膨胀:在多数设备上都存在性能问题。
  • 频繁垃圾回收:通过内存变化图进行分析。

    监控内存的几种方式

  • 浏览器任务管理器(快捷键Shift+Esc)

  • Timeline时序图记录 (通过performance)
  • 堆快照查找分离DOM(通过Memory的堆快照中的Detached)
  • 判断是否存在频繁的垃圾回收

    • Timeline中频繁的上升下降
    • 任务管理器中数据频繁的增加减少

      JavaScript代码优化

  • 避免声明不必要的全局变量。(用let、const替代var )

  • 减少全局查找,变量声明能放局部就放局部,如果要多次访问全局的变量对象可以先将它存到当前作用域的局部变量中。
  • 避免循环引用,也就是相互引用的现象,将不要引用的那方置为null
  • 对象,数组声明用字面量替换new
  • 用setTimeout替换setInterval ???
  • 批量绑定时间可以用事件委托来完成