JS 垃圾回收

176 阅读4分钟

垃圾回收(Garbage Collection)是一种自动的内存管理机制,当我们不再需要这个东西的时候,它会定期地被浏览器回收,不再占用内存。

以下内容主要参考:javascript.info/garbage-col…

一、判断什么是垃圾

垃圾:不再需要的一个东西,需要从内存中释放。

基本规则

1.全局变量都不是垃圾,都不能回收,任何时刻都有可能再次用到它。

var a=1 

2.当前正在执行的函数,其内部的局部变量和参数都不是垃圾

所有变量都有生命周期,只有函数fn执行的时候,b才产生在内存里,局部变量在函数执行完退出的时候就变成了垃圾,没有可能再用到它,下次再调用函数的时候,产生的是新的局部变量b。

var a=1            //全局变量,不是垃圾
function  fn(){
  var b=2        
  console.log(b)  
}
fn()              //第一次调用fn  内存中出现局部变量b   函数执行完成之后,b就变成了垃圾被回收
console.log(a)    
fn()              //第二次调用fn  产生新的局部变量b,不再沿用之前的b

3.永远不会变成垃圾的:

  • window:永远都会可能用到,上面挂载了很多东西。window.Object/Array/Promise
  • 全局作用域的变量:上例中的全局变量a、全局函数fn

引用 Reference

单引用

如果全局变量中有一个对象a,该对象a被认为是可访问的,并且对象a引用另一个对象的属性b,那么b也是可访问的,它们都不会被当成垃圾回收,详细示例如下:

let user = {
  name: "John"
};

全局变量"user"引用该对象{name: "John"},二者都是可访问的。

image-20220301153435965.png

如果user的值被置为null,则引用丢失{name: "John"}成为垃圾被回收:

user = null;

image-20220301153534172.png

双引用

将引用从user复制一份到admin

let user = {
  name: "John"
};
let admin = user;
user = null;   

user值置为null,{name: "John"}仍然可以通过admin全局变量访问,所以它仍在内存中。如果我们也置空了admin,那么{name: "John"}就可以被当成垃圾回收,从内存中删除。

image-20220301153827940.png

相互关联引用

这是一段比较复杂的引用:

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;
  return {
    father: man,
    mother: woman
  }
}
let family = marry({ name: "John"}, { name: "Ann" });  //一个新对象

下图中的所有对象都是可访问的:

image-20220301154058322.png

删除两个引用后:

delete family.father;
delete family.mother.husband;

一个对象没有被指向的引用,仅有指向别人的引用是不能被访问的,会成为垃圾被回收。因此对象{ name: "John"}不能够被访问,成为垃圾:

image-20220301154207656.png

垃圾回收后,真实留在内存中的:

image-20220301154538064.png

闭环引用

虽然每个对象都有被其他对象引用,但是整个相互关联的对象形成闭环,仍然是外界无法访问的,会成为垃圾被回收。将上例的初始状态修改为:

family = null;

内存将会变成:

image-20220301154816997.png

二、浏览器如何进行垃圾回收

有两种基本的垃圾收集算法,分别采用了遍历计数的思想。

标记-清除算法(Mark-and-Sweep)

浏览器从全局作用域(根)开始,找全局变量,去找它们的引用,一层层的向下标记,如果这些变量引用了其他的变量就接着再向下标记,一直遍历找到到所有可访问的对象,最后,将未标记的对象清除掉。

image-20220301155136017.png

  • 优点:简单
  • 缺点:速度慢。整体遍历,再标记。如果对象太多,标记会很慢,如果后面代码中把某个引用删掉,每次都需要重新标记。

改进:

  1. 分代收集。对象分成新代(临时的函数调用,马上标记马上删除)和老代(window对象),检查新老对象的频率不同,如果一个对象停留的时间越久,那么后面用到它的时间可能也越久,就隔很久再去看一次。
  1. 增量收集。一次只检查1000个节点,然后执行JS,过后再遍历1000个,再执行JS......,等把所有对象遍历完再垃圾回收。
  1. 空闲时间收集。等JS不执行的时候收集

引用计数算法(Reference Counting)

记录每个对象被引用的次数,全局变量都初始记为1,每次这个对象被创建引用就+1,删除引用了就-1,引用为0时就可以垃圾回收。

  • 优点:可即刻回收垃圾,时间短,速度快,不用多次遍历。
  • 缺点:计数任务繁重。