这篇文章主要总结了Javascript中垃圾回收机制的一些要点。
Javascript中的垃圾回收机制
目录
Javascript中的垃圾回收机制负责回收内存。
这种垃圾收集机制的原理其实很简单:找出那些不再继续使用的变量,然后释放其占用的内存。
为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。
Javascript中的垃圾回收机制有2种垃圾收集方式:
标记清除(mark-and-sweep)
引用计数(reference counting)
标记清除
标记清除是最常用的垃圾收集方式。
标记和清除的具体过程:
- 当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。
- 当变量离开环境时,则将其标记为“离开环境”。
- 一开始,垃圾收集器在运行的时候会给存储在【内存中的所有变量】都加上标记(此时标记的含义为“进入环境”)。
- 然后,垃圾收集器会去掉【执行环境中】(注意“执行环境中”和“内存中”是不同的)的变量以及【被执行环境中的变量引用】的变量的标记(去掉“进入环境”的标记,因为这些变量已经加入到环境中了)。
- 在此之后,再被加上标记的变量(此时的标记含义为“离开环境”)将会被视为准备删除的变量。原因是环境中的变量已经无法访问到这些变量了。
- 最后,垃圾收集器完成内存清除工作,销毁那些带标记(“离开环境”的标记)的变量(值),并回收它们所占用的内存空间。
引用计数
引用计数的含义是跟踪记录每个值被引用的次数。
引用计数的具体过程:
- 当声明了一个变量,并将一个引用类型值赋给变量时,则这个值的引用次数就是1。
- 如果同一个值又被赋给另一个变量,则该值的引用次数再加1。
- 相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。
- 当这个值的引用次数变成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章