这篇文章主要总结了Javascript中垃圾回收机制的一些要点。

Javascript中的垃圾回收机制

目录

  1. 标记清除
  2. 引用计数
  3. 优化内存占用的最佳方式

Javascript中的垃圾回收机制负责回收内存。

这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变量,然后释放其占用的内存。

为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。

Javascript中的垃圾回收机制有2种垃圾收集方式:

  1. 标记清除(mark-and-sweep)

  2. 引用计数(reference counting)

标记清除

标记清除是最常用的垃圾收集方式。

标记和清除的具体过程:

  1. 当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。
  2. 当变量离开环境时,则将其标记为“离开环境”。
  3. 一开始,垃圾收集器在运行的时候会给存储在【内存中的所有变量】都加上标记(此时标记的含义为“进入环境”)。
  4. 然后,垃圾收集器会去掉【执行环境中】(注意“执行环境中”和“内存中”是不同的)的变量以及【被执行环境中的变量引用】的变量的标记(去掉“进入环境”的标记,因为这些变量已经加入到环境中了)。
  5. 在此之后,再被加上标记的变量(此时的标记含义为“离开环境”)将会被视为准备删除的变量。原因是环境中的变量已经无法访问到这些变量了。
  6. 最后,垃圾收集器完成内存清除工作,销毁那些带标记(“离开环境”的标记)的变量(值),并回收它们所占用的内存空间。

引用计数

引用计数的含义是跟踪记录每个值被引用的次数。

引用计数的具体过程:

  1. 当声明了一个变量,并将一个引用类型值赋给变量时,则这个值的引用次数就是1。
  2. 如果同一个值又被赋给另一个变量,则该值的引用次数再加1。
  3. 相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。
  4. 当这个值的引用次数变成0时,则说明没有变量引用这个值了(即没有办法再访问这个值了),则可以将其占用的内存空间收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

引用计数策略所存在的问题和解决办法

引用计数这个方式并不太常用,而且还会出现循环引用的问题:假设在一个函数中,定义了对象A和对象B,并且,对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用,此时对象A、B相互引用,也就是说,这两个对象的引用次数都为2。

  • 如果采用标记清除策略,在函数执行完毕后,对象A、对象B都离开了环境,即标记上了“离开环境”标志,所以它们的相互引用不会影响垃圾收集器对它们的回收,垃圾回收照常进行。
  • 而用引用计数的策略的话,就有出现问题了,当函数执行完毕后,它们两个还会继续存在,因为它们两个相互引用,所以引用次数永远不会是0,也就是说垃圾收集器永远不会回收这两个对象。。假如这个函数被重复多次调用,就会导致大量内存得不到回收。

解决循环引用的办法还是有的,那就是在最后手动把变量的值设成null(解除引用)obj = null;。将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

优化内存占用的最佳方式

确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null 来释放其引用——这个做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开执行环境时自动被解除引用。

function createPerson(name){
  var localPerson = new Object();  // localPerson是局部变量,在函数执行完后就会自动被解除引用
  localPerson.name = name;
  return localPerson;
}
var globalPerson = createPerson("Allenmind");
// 手工解除globalPerson 的引用
globalPerson = null;

不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收

本文参考文献

《Javascript高级程序设计 第3版》第4章