内存泄露
内存泄露
一、4种常见的内存泄露
- 浏览器垃圾回收机制,但并不是说我们就可以完全不用关心垃圾回收这块了,我们的代码中依然要主动避免一些不利于引擎做垃圾回收操作,因为不是所有无用对象内存都可以被回收的,那当不再用到的内存,没有及时回收时,我们叫它
内存泄漏
1.1.意外的全局变量
未声明的变量-window全局变量
当我们在一个函数中给一个变量赋值,但是没有声明它时
function Fn(){
a = "hello";
}
此时这个全局变量a相当于是window对象下的一个变量:
function Fn(){
window.a = "hello";
}
注:全局变量很难被垃圾回收器回收,所以会进行内存泄露。
使用this创建的变量还有种情况
function Fn(){
this.a = "hello";
}
Fn();
当然,这个this也是指向的window,因为此时创建的a变量会挂载到window下
解决方式是:
- 在JS文件头部或者函数的顶部加上严格模式 use strict 然后this的指向为undefined,这样就可以避免。
注:如果必须使用全局变量存储大量数据,确保用完之后把它设置为null或者重新赋值定义。
1.2.被遗忘的计时器,或回调函数
定时器引起
当我们在代码中使用定时器也有可能会造成内存泄漏
var serverData = loadData();
setIntervalDemo = setInterval(function(){
var renderer = document.getItemById('renderer');
if(renderer){
render.InnerHTML = JSON.stringify(serverData)
}
},5000)
- 上面的例子中,节点rederer引用了serverData,在节点renderer或者数据不再需要时,定时器依旧指向这些数据。
- 哪怕当renderer节点被移除过后,定时器interval依旧存活,所以垃圾回收器没办法回收,那么他的依赖也没法回收。
解决方式是:
- 终止定时器。clearInterval(setIntervalDemo)
对象观察者
还有个就是关于观察者模式的案例:
var btn - document.getElementById('btn');
function onClick(element){
element.innerHTML = 'I`m innerHTML';
}
btn.addEventListener('click',onClick);
一旦他们不再需要(或者关联的对象变成不可达),明确的移除他们非常必要。因为他要一直阻塞在那里监听,浪费内存。
解决方法:
- 除了现在新浏览器的垃圾回收算法可以移除,建议还是把它写上为好
btn.removeEventListener('click',onClick);
1.3.脱离DOM的引用
如果把DOM存成字典(JSON键值对)或者数组,此时同样的DOM元素存在两个引用:
- 一个在DOM树中
- 另一个在字典中
那么两个都需要删除
例如:
//在对象中引用DOM
var elements = {btn:document.getElementById('btn')}
function doSomeThing(){
elements.btn.click();
}
function removeBtn(){
//将body中的btn移除,也就是移除DOM树中的btn
document.body.removeChild(document.getElementById('button'));
//但是此时全局变量elements还是保留了btn的引用,并且还赋值给了这个字典,这个DOM元素还是在内存中,不能回收。
}
解决方式:
- 手动设置空指针移除:element.btn = null;
1.4.闭包
闭包,也就是局部变量销毁时
首先我们明确一点,闭包的关键就是匿名函数能够访问父级作用域中的变量
function fn(){
var a = 'I am a';
return function(){
console.log(a);
}
}
//因为a被匿名函数所引用,所以这种变量不会被回收
如果情况复杂一点
var globalVar = null;//全局
var fn = function(){
var originVal = globalVar;//局部变量
var unused = function(){
if(originVal){
console.log('call')
}
}
globalVar = {
longStr:new Array(1000000).join('*'),
someThing:function(){
console.log('someThing')
}
}
}
setInterval(fn,100)
以上案例你会发现:
- 每次调用fn函数时候都会产生一个新对象originVal
- 变量unused是一个引用了originVal的闭包
- unused虽然未被使用,但是它引用的originVal迫使它留在内存中,并且不会回收
解决方式:
- 每次执行完对originVal进行销毁,也就是赋值空指针
二、内存泄漏的识别方法
- Chrome浏览器的控制台Performance或Memory
- Node提供的process.memoryUsage方法
2.1.Chrome浏览器的控制台Performance或Memory
Chrome 浏览器查看内存占用,按照以下步骤操作。
在网页上右键, 点击“检查”打开控制台(Mac快捷键option+command+i);选择Performance面板(很多教材中用的是Timeline面板, 不知道是不是版本的原因);勾选Memory, 然后点击左上角的小黑点Record开始录制;点击弹窗中的Stop结束录制, 面板上就会显示这段时间的内存占用情况。
2.2.Node提供的process.memoryUsage方法
console.log(process.memoryUsage());
// { rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772 }
process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。
该对象包含四个字段,单位是字节,含义如下:
rss(resident set size):所有内存占用,包括指令区和堆栈。heapTotal:"堆"占用的内存,包括用到的和没用到的。heapUsed:用到的堆的部分。external: V8 引擎内部的 C++ 对象占用的内存。
判断内存泄露, 是看heapUsed字段.
三、总结
3.1.内存泄漏包括:
- 意外的全局变量,直接赋值,或者通过this绑定到window的全局变量
- 被遗忘的计时器,或者回调函数或者监听器(相当于是局部作用域链的全局变量,因为这些被遗忘的里面要使用该变量)
- 脱离DOM的引用(DOM树、字典中)
- 闭包中重复创建的变量
3.2.避免内存泄漏:
- 注意程序逻辑,避免死循环。
- 减少不必要的全局变量,或者相对作用域链上的全局变量,或者生命周期很长的对象,即时回收。
- 避免频繁创建过多的对象,用完了记得销毁。
作者:黄哈哈。
原文链接:https://www.cnblogs.com/jiajia-hjj/p/16024119.html
本博客大多为学习笔记或读书笔记,本文如对您有帮助,还请多推荐下此文,如有错误欢迎指正。