跳到主要内容

垃圾回收

垃圾回收(GC)

找出那些不再继续使用的变 量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间), 周期性地执行这一操作。

什么是垃圾​

  • 一般来说没有被引用的对象就是垃圾,就是要被清除

垃圾回收的算法是什么​

引用计数​

早期浏览器使用的是引用计数

  • 记录对象被引用的次数。当一个对象的引用计数变为零时,即没有任何指针指向该对象,系统就会认为该对象已不再被使用,可以安全地释放其所占用的内存空间

引用计数会有什么缺陷?​

  • 循环引用问题,引用计数无法清除
    • 即对象之间互相引用形成环,导致引用计数一直不为零,从而造成内存泄漏

标记清除​

  • 它通过追踪对象的引用关系,找出所有不再被引用的对象

  • GC会检测当前对象有没有被变量所引用,如果没有就回收

标记清除的优点是可以处理循环引用的情况,但缺点是会产生内存碎片,影响内存的连续分配

JavaScript 的内存管理​

不管是什么样的计算机程序语言,运行在对应的代码引擎上,对应的使用内存过程大致逻辑是一样的,可以分为这三个步骤:

1.分配你所需要的系统内存空间;

2.使用分配到的内存进行读或者写等操作;

3.不需要使用内存时,将其空间释放或者归还。

  • JavaScript与其他语言不一样的是,JavaScript 中,当我们创建变量(对象,字符串等)的时候,系统会自动给对象分配对应的内存
  • 系统经过一段时间发现这些变量不会再被使用的时候,会通过垃圾回收机制的方式来处理掉这些变量所占用的内存

对于简单的数据类型,内存是保存在栈(stack)空间中的;复杂数据类型,内存保存在堆(heap)空间中。简而言之,基本就是说明以下两点。

基本类型:这些类型在内存中会占据固定的内存空间,它们的值都保存在栈空间中,直接可以通过值来访问这些;

引用类型:由于引用类型值大小不固定(比如上面的对象可以添加属性等),栈内存中存放地址指向堆内存中的对象,是通过引用来访问的。

因此总结来说:栈内存中的基本类型,可以通过操作系统直接处理;而堆内存中的引用类型,正是由于可以经常变化,大小不固定,因此需要 JavaScript 的引擎通过垃圾回收机制来处理。

栈内存和堆内存的垃圾回收​

栈内存中变量一般在它的当前执行环境结束就会被销毁被垃圾回收制回收

而堆内存中的变量则不会,因为不确定其他的地方是不是还有一些对它的引用。 堆内存中的变量只有在所有对它的引用都结束的时候引用次数为0才会被回收

如何处理垃圾 【浏览器如何处理垃圾】​

(1)标记清除​

通过名字你就可以理解,标记清除分为两个阶段:标记阶段和清除阶段。

首先它会遍历堆上的所有的对象,分别对它们打上标记;然后在代码执行过程结束之后,对使用过的变量取消标记。那么有标记的就是没有使用过的变量,因此在清除阶段,就会把还有标记的进行整体清除,从而释放内存空间。

  • 缺点:出现内存碎片

(2)引用计数​

text
const a = Object.create({name:10})
const b = a
a = null
b = null

跟踪记录每 个值被引用的次数。清除引用次数为0的变量 ⚠️会有循环引用问题 。循环引用如果大量存在就会导致内存泄露。

  • 引用计数,就是记录每个对象被引用的次数,每次新建对象、赋值引用和删除引用的同时更新计数器,如果计数器值为0则直接回收内存。 很明显,引用计数最大的优势是暂停时间短

引用计数算法优点:

  • 【及时回收】引用计数为零时,发现垃圾立即回收;
  • 最大限度减少程序暂停;

引用计数算法缺点:

  • 无法回收循环引用的对象;
    • 如果2个对象互相引用,互相占用,尽管它们已不再被使用,但是引用计数无法识别,导致内存泄漏
  • 空间开销比较大;

内存泄漏​

  • 内存泄漏是指一段程序对内存失去控制导致内存无法释放,从而导致内存泄漏 【失去控制 】

内存泄漏的识别方法​

  • Chrome 浏览器查看内存占用
    • 打开开发者工具,选择 Timeline 面板
      • Timeline面板中,你可以看到web 应用内的耗时分布,例如执行DOM事件、渲染页面布局或者绘制页面元素等
      • 开启Timeline快捷键是 Ctrl + E 或 Cmd + E
    • 在顶部的 Capture字段里面勾选 Memory
    • 点击左上角的录制按钮。
    • 在页面上进行各种操作,模拟用户的使用情况。
    • 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。

解决办法​

及时清除引用非常重要。但是,你不可能记得那么多,有时候一疏忽就忘了,所以才有那么多内存泄漏。

最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻程序员的负担,你只要清除主要引用就可以了

  • ES6 考虑到了这一点,推出了两种新的数据结构:WeakSet 和 WeakMap,它们对于值的引用都是不计入垃圾回收机制的,所以名字里面才会有一个"Weak",表示这是弱引用,可以解决内存泄漏

内存泄漏的场景​

1.过多的缓存未释放;​

2.闭包太多未释放;​

3.定时器或者回调太多未释放;​

  • 需要清除定时器

4.太多无效的 DOM 未释放;​

  • 在你使用完数据后,及时解除引用
text
var elements = {
image:document.querySelector('.li')
}
//解除引用
element.image = null

5.全局变量太多未被发现。​

  • 减少不必要的全局变量,使用严格模式避免意外创建全局变量
  • 全局的变量是不会被垃圾回收掉的
  • 全局的变量是绑定到window对象上的,在浏览器就是window,即页面中的全局变量,只有页面被关闭了才会被销毁

执行fn函数,就定义了2个全局变量,age和name,都挂载到了window对象

js
function fn(){
age = '20'
this.name = 'qqiu'
}
fn()

6.控制台的打印​

  • 控制台的打印也会造成内存泄漏
  • 浏览器会一直保存打印对象的信息
  • 因此打印数据过多,或者打印出很大的数组对象都会造成内存泄漏