内存泄漏
(
Memory leak
)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使⽤的
内存
并⾮指内存在物理上的消失,⽽是应⽤程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从⽽造成了内存的浪费
程序的运⾏需要内存。只要程序提出要求,操作系统或者运⾏时就必须供给内存
对于持续运⾏的服务进程,必须及时释放不再⽤到的内存。否则,内存占⽤越来越⾼,轻则影响系统性能,重则导致进程崩溃
⼤多数语⾔提供⾃动内存管理,减轻程序员的负担,这被称为"垃圾回收机制"
垃圾回收机制
Javascript
具有⾃动垃圾回收机制(
GC
:
Garbage Collecation
),也就是说,执⾏环境会负责管理代码
执⾏过程中使⽤的内存
原理:垃圾收集器会定期(周期性)找出那些不在继续使⽤的变量,然后释放其内存
通常情况下有两种实现⽅式:
- 标记清除
- 引⽤计数
标记清除
JavaScript
最常⽤的垃圾收回机制
当变量进⼊执⾏环境是,就标记这个变量为
“
进⼊环境
“
。进⼊环境的变量所占⽤的内存就不能释放,当变量离开环境时,则将其标记为“
离开环境
“
垃圾回收程序运⾏的时候,会标记内存中存储的所有变量。然后,它会将所有在上下⽂中的变量,以及 被在上下⽂中的变量引⽤的变量的标记去掉
在此之后再被加上标记的变量就是待删除的了,原因是任何在上下⽂中的变量都访问不到它们了
随后垃圾回收程序做⼀次内存清理,销毁带标记的所有值并收回它们的内存
举个例⼦:
var m = 0,n = 19 // 把 m,n,add() 标记为进⼊环境。
add(m, n) // 把 a, b, c标记为进⼊环境。
console.log(n) // a,b,c标记为离开环境,等待垃圾回收。
function add(a, b) {
a++
var c = a + b
return c }
引⽤计数
语⾔引擎有⼀张
"
引⽤表
"
,保存了内存⾥⾯所有的资源(通常是各种值)的引⽤次数。如果⼀个值的引 ⽤次数是 0
,就表示这个值不再⽤到了,因此可以将这块内存释放
如果⼀个值不再需要了,引⽤数却不为
0
,垃圾回收机制⽆法释放这块内存,从⽽导致内存泄漏
const arr = [1, 2, 3, 4];
console.log('hello world');
代码中,数组
[1, 2, 3, 4]
是⼀个值,会占⽤内存。变量
arr
是仅有的对这个值的引⽤,因此引⽤
次数为
1
。尽管后⾯的代码没有⽤到
arr
,它还是会持续占⽤内存
如果需要这块内存被垃圾回收机制释放,只需要设置如下:
arr = null
通过设置
arr
为
null
,就解除了对数组
[1,2,3,4]
的引⽤,引⽤次数变为
0
,就被垃圾回收了
⼩结
有了垃圾回收机制,不代表不⽤关注内存泄露。那些很占空间的值,⼀旦不再⽤到,需要检查是否还存 在对它们的引⽤。如果是的话,就必须⼿动解除引⽤
常⻅内存泄露情况
意外的全局变量
function foo(arg) {
bar = "this is a hidden global variable"; }
另⼀种意外的全局变量可能由
this
创建:
function foo() {
this.variable = "potential accidental global"; }
// foo 调⽤⾃⼰,this 指向了全局对象(window)
foo();
上述使⽤严格模式,可以避免意外的全局变量
定时器也常会造成内存泄露
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// 处理 node 和 someResource
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
如果
id
为
Node
的元素从
DOM
中移除,该定时器仍会存在,同时,因为回调函数中包含对
someResource
的引⽤,定时器外⾯的
someResource
也不会被释放
包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放
function bindEvent() {
var obj = document.createElement('XXX');
var unused = function () {
console.log(obj, '闭包内引⽤obj obj不会被释放');
};
obj = null; // 解决⽅法
}
没有清理对
DOM
元素的引⽤同样造成内存泄露
const refA = document.getElementById('refA');
document.body.removeChild(refA); // dom删除了
console.log(refA, 'refA'); // 但是还存在引⽤能console出整个div 没有被回收
refA = null;
console.log(refA, 'refA'); // 解除引⽤
包括使⽤事件监听
addEventListener
监听的时候,在不监听的情况下使⽤
removeEventListener
取消对事件监听