# 垃圾回收机制

引用计数

广度优先 栈区

内存泄露 闭包

# 浏览器

  • 浏览器怎样进行垃圾回收
  • 浏览器中不同类型变量的内存都是何时释放
  • 哪些情况会导致内存泄露,如何避免
  • weakMap weakSet 和 Map Set 有什么区别

# 什么是垃圾数据

垃圾回收

let dog = new Object();
dog.a = new Array(1);
dog.a = "b";

这时候的 Array 就成了不被使用的数据,专业名词叫[不可达]的数据。 这就需要回收的垃圾数据。

# 垃圾回收算法

# 第一步:标记空间中[可达]值

V8 采用的是可达性(reachability)算法来判断堆中的对象应不应该被回收。 这个算法的思路是:

  • 从根节点(Root)出发,遍历所有的对象
  • 可以遍历到的对象,是可达的(reachable)
  • 没有被遍历到的对象,是不可达的(unreachable)

在浏览器环境下,根节点很多,主要包括

  • 全局变量 window,位于每个 iframe 中
  • 文档 DOM 树
  • 存放在栈上的变量
  • 。。。

这些根节点不是垃圾,不可能被回收

# 第二步:回收[不可达]的值所占据的内存

在所有标记完成之后,统一清理内存中所有不可达对象

# 第三步:做内存整理
  • 在频繁回收对象后,内存中就会存在大量不连续空间,专业名词叫内存碎片
  • 当内存中出现大量的内存碎片,如果需要分配较大的连续内存时,就有可能出现内存不足的情况
  • 所以最后一步是整理内存碎片(但这步是可选的,因为有的垃圾回收器不会产生内存碎片,比如接下来我们要介绍的副垃圾回收器)

# 什么时候垃圾回收

浏览器进行垃圾回收的时候,会暂停 JavaScript 脚本,等垃圾回收完毕再继续执行。
对于普通应用,这样没什么问题,但对于 js 游戏、动画对连贯性要求比较高的应用,如果暂停时间很长就会造成页面的卡顿。

# 分代收集

浏览器将数据分为两种,一种是临时对象,一种是长久对象。

  • 临时对象
    • 大部分对象在内存中存活的时间很短
    • 比如函数内部声明的变量,或块级作用域中的变量。当函数或代码块执行结束后,作用域中定义的变量就会被销毁。
    • 这类对象很快就会变的不可访问,应该快点回收
  • 长久对象
    • 生命周期很长的对象,比如全局的 window、dom、web API 等等
    • 这类对象可以慢点回收

这两种对象对象不同的回收策略,所以,v8 把堆分为新时代和老生代的两个区域。新时代中存放临时对象,老生代中存放持久对象。并且让副垃圾回收器、主垃圾回收器,分别负责新生代和老生代的垃圾回收。

# 主垃圾回收器

负责老生代的垃圾回收,有两个特定

    1. 对象占用空间大
    1. 对象存活时间长

它使用标记-清除的算法执行垃圾回收

    1. 首先是标记
    1. 从一组根元素开始,递归遍历这组根元素
    1. 在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素可以判断为垃圾数据
    1. 直接将标记为垃圾的数据清理掉
    1. 多次标记-清除,会产生大量不连续的内存碎片,需要进行内存整理

# 副垃圾回收器

负责新生代的垃圾回收,一般只支持 1-8M 的容量 新生代被分为两个区域(semisspace):一半是对象区域(From),一半是空闲区域(To) 新加入的对象都被放入对象区域,等对象区域快满的时候,会执行一次垃圾清理

  • 先给对象区域所有垃圾做标记
  • 标记完成后,存活的对象被复制到空闲区域,并且将他们有序的排列一遍
  • 副垃圾回收器没有碎片整理,因为空闲区域里此时是有序的,没有碎片,也就是不需要整理了。
  • 复制完成后,对象区域会和空闲区域进行对调,将空闲区域中存活的对象放入对象区域中
  • 因为副垃圾回收器操作比较频繁,所以为了执行效率,一般新生代的空闲会被设置的比较小。
  • 是典型的空间换时间的方法

# 分代收集

将堆分为新生代和老生代,多回收新生代,少回收老生代。-> 这样就减少每次需要遍历的对象,从而减少每次垃圾回收的耗时

# Scavenge 算法

在分代基础上,新生代的对象主要通过 Scavenge 算法进行垃圾回收,在具体实现时主要采用 cheney 算法,cheney 算法是一种采用复制的方法实现垃圾回收算法。

# markSweep 和 markCompact

老生代使用的方法

# 增量收集

如果脚本中有许多对象,引擎一次性遍历整个对象,会造成一个长时间的暂停。所以引擎将垃圾收集工作分为更小的块,每次处理一部分,多次处理。

# 闲时收集

垃圾收集器只会在 CPU 空间时尝试运行,以减少可能对代码执行的影响。

# 面试题

  • 浏览器是怎么进行垃圾回收的
    • 什么是垃圾回收
      • 不在需要的,即是垃圾,全局变量随时可能用到,所以不一定是垃圾
    • 如何捡垃圾
      • 从根节点出发,遍历所有对象,可达的不是垃圾,不可达的是垃圾
      • 回收不可达的值所占据的内存
      • 做内存整理
    • 什么时候捡垃圾
      • 垃圾回收会暂停浏览器的运行,所以采用分代收集、增量收集、闲时收集的策略
  • 浏览器中不同类型变量的内存都是何时释放的
    • JavaScript 中类型:值类型和引用类型
      • 引用类型: 在没有引用之后,V8 自动回收
      • 值类型:
        • 如果处于闭包,要等闭包没有被引用才会被 v8 回收
        • 非闭包的情况下,等待 V8 的新时代的切换的时候回收
  • 哪些情况会造成内存泄露,如何避免
    • 无限制增长的数组
    • 无限制设置属性和值
    • 任何模块内的私有变量和方法均是永驻内存的 a=null
    • 大循环,无 GC(垃圾回收机制)机会
  • weakMap weakSet 和 Map Set 有什么区别
    • 因为是弱引用,所有可以解决内存泄露的问题
    • weakMap weakSet 的键名所引用对象都是弱引用,就是垃圾回收机制遍历的时候不考虑该引用
    • 只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存

# ESP

ESP(Extended Stack Pointer)为扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针。与之对应的是 EBP(Extended Stack Point),扩展基指针寄存器,也被称为帧指针寄存器,用于存放函数栈底指针。

# node

node 使用 JavaScript 在服务端操作最大内存对象受到一定限制(堆区),64 位系统下约为 1.4GB,32 位系统约 0.7GB,新生代 64 位是 32M,32 位是 16M

> node --huge-max-old-generation-size index.js

# node 内存泄露分析

node-inspector
console.log("Sever PID", process.pid);
sudo node --inspect app.js
top -pid 2322