JS垃圾回收和内存泄露

2,701 阅读5分钟
1.JS垃圾回收有几种方式
2.什么方式会引起内存泄露
3.如何避免内存泄漏,有几种方式
4.JS的栈内存和堆内存

垃圾回收的方式

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

JS具有自动垃圾回收机制,GC(Garbage collection)不是实时的,因为开销比较大,所以回收会按照固定的时间间隔周期性执行。

  • 回收方法一:标记清除
    • 当变量进入环境时,标记为“进入环境”,离开环境时,标记为“离开”
    • 垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。
    • 然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。
    • 而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
    • 最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
  • 回收方法二:引用计数
    • IE7、8是用的这种方式,不推荐,IE9把DOM和BOM转换为真正的JS对象了,所以不再使用引用计数
      • IE中的DOM和BOM的实现使用了C++的COM,而COM的回收机制是引用计数
    • 原理:跟踪记录每个值被引用的次数,当声明一个变量,并将这个引用类型的值赋值给该变量时,这个引用类型值的引用次数就是1
      • var a = {}; // a的引用次数是1
      • 当引用次数变为0时,就会释放该内存值占用的空间
    • 循环引用时会造成内存泄露
      • 循环引用:对象A中包含指向对象B的指针,B中包含指向A的引用,当程序执行结束,引用计数不为0,导致无法释放A、B
      • 解决方案:手动设置为null,切断变量与它此前引用的值之间的连接,GC在下次运行时,就会删除这些值,并把它们回收
  • 浏览器的回收策略--标记清除,但是垃圾收集的时间间隔,各个浏览器互不相同。

内存泄漏的情况

  • 内存泄漏:不再用到的内存,没有及时释放
  • 引起内存泄露的情况
    • 垃圾回收不会清除全局变量,全局变量会造成内存泄露
      • 原因:局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数结束并不能算是结束。 而全局变量的生命周期直至浏览器卸载页面才会结束。
      • 解决方法:使用严格模式
    • 未销毁的定时器和回调函数,造成内存泄露
      • 定时器的回调函数是个闭包,闭包会造成引用的变量和函数一直保存在内存中,无法释放,造成内存泄露
      • 解决方法:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。
    • DOM元素的不恰当处理
      • 虽然别的地方删除了,但对象中还是存在对DOM的引用,比如引用了td元素,但删除了整个表格,实际上td元素仍然保留对其父元素的引用,导致整个表格都无法回收
      • 解决方法:对于DOM元素的引用要及时手动置空

触发垃圾回收的机制

  • IE6--当环境中存在256个变量、4096个对象、64k的字符串任意一种情况的时候就会触发垃圾回收器工作
    • 当环境中一直存在这么多变量时,GC会一直在工作
  • IE7--触发条件动态修改,根据垃圾回收期回收的内存分配量和程序的内存量比较
  • 垃圾回收时,也没回停止响应,造成页面卡顿,如何优化呢?
    • 方法1:分代回收
      • 变量存储时区分临时、持久区域,多回收临时对象区,少回收持久对象区,减少每次需要遍历的对象,从而减少每次GC耗时
    • 方法2:增量GC
      • 每次处理一点,下次再处理一点,虽然耗时短,但是中断较多,需要上下文频繁切换。
    • 针对不同场景,可以选择不同的处理方案

堆内存和栈内存

JS的数据类型有7中,其中Number、Boolean、String、Null、Symbol都是基本类型,Object、Array是引用类型。

  • 栈内存保存基本类型的值、指向引用类型的指针,内存大小是固定的
  • 堆内存保存的是引用类型的值,如数组和对象,内存大小是不固定的
  • 栈内存是先进后出,堆内存是按引用地址读取
  • 操作对象时,实际操作的是对象的引用,而不是实际的对象
  • 堆内存由程序员分配,若程序员不释放,程序结束时会有OS回收,分配方式类似链表
  • 栈内存由OS自动操作释放,使用的是一级缓存,被调用时处于存储空间,调用完毕立刻释放
  • 堆内存放在二级缓存中,生命周期由垃圾回收算法来决定,并不是一旦成为孤儿就会被回收。