以前在携程的时候专门做了一次关于v8引擎垃圾回收相关的调研和分享,这方面文章非常少,在这里写出来,也算是记录一下。
啥是垃圾回收?
垃圾回意思是,GC会尝试去重新分配已经不需要的对象所占据的内存空间。如果你的程序中仍然存在指向一个对象的引用,那么该对象将不会被回收。
特点是:ECMAScript没有暴露任何垃圾回收器的接口,我们无法强迫其进行垃圾回收,更无法干预内存管理。
所以我们没有办法使用JS去主动操作GC,在大多数的情况下,我们也没有必要去手动得解除对象的引用。只要简单地把变量放在它们应该的地方(局部变量的方式),垃圾就能正确地被回收。
V8内存分配及限制
chrome V8 中,所有的 JavaScript 对象都是通过堆内存來进行內存分配的。(大家可以看下堆、栈的区别)
原理:当声明变量并赋值时,V8 就会在堆內存中分配一部分给这个变量。如果已申请的內存不足以存储这个变量,V8 就会继续申请,直到堆的大小达到了 V8 的內存上限为止。
特意查了一下,默认情況下,V8 的堆內存的大小上限在64位系統中为 1.4G,在32位系統中則為0.7G左右。
这个不是非常准确,也没有官方发布。我查了一下google 技术博客,
堆内存分配的几种形式
- 静态分配:全局变量,函数之类的分配方式,由于他们页面没有关闭之前,他们是不会被清除的。(代码编译时)
- 动态分配:使用new创建出来的,主动要求给分配空间的。(代码执行时)
经验法则
为了使垃圾回收器尽早回收尽可能多的对象,不要保留不再需要的对象。这里有几点需要谨记:
- 在恰当的作用域中使用变量。尽量在函数作用域中声明变量,而尽可能不要声明不会被回收的全局变量,这将意味着更干净更省心的代码。
- 确保解绑那些不再需要的事件监听器。尤其是那些即将被移除的 DOM 对象所绑定的事件。
- 避免缓存中储存大量不会被重用的数据。
- 少用闭包
最后写一下JS中会常驻内存的一些写法:
1、全局变量
尽量避免全局变量。全局变量 常驻内存,GC不能回收。
全局对象只会在刷新页面、导航到其他页面、关闭标签页或退出浏览器时才会被清理。函数作用域的变量将在超出作用域时被清理,即退出函数时,已经没有任何引用,这样的变量就被清理了。
2、JS对象引用
function ob(){
var bar = new largeObject(); //很大一个对象\变量\字符串
bar.someCall();
return bar;
}
var a = new ob();
现在有一个指向 bar 对象的引用,当 ob 调用结束后,bar 对象不会被回收,
直到变量a分配其他引用(或者 a 超出了作用域范围)。复制代码
3、DOM 事件(目前使用vue,react框架等已经很少直接操作dom了,可以忽略了)
var c= document.getElementById('content');
c.innerHTML = '<button id="button">click</button>';
var b= document.getElementById('button');
b.addEventListener('click', function(){});
content.innerHTML = '';
虽然 <button> 从 DOM 中移除了,由于它的监听器还在,所以无法被 GC 回收。
要避免这种情况就是通过 removeEventListener 将回调函数去掉。复制代码
4、定时器,定时器不用的时候记得要clearTimeout
应该还有其他一些,就不举例子了
Chrome DevTools中提供了很多内存分析工具,内存快照等,关注devtools中的performance、memory、take heap snapshot等