因为项目需要涉及到了垃圾回收机制和内存泄漏,查看网上了许多教程,未免以后遗忘故此记录下来以便以后查阅。
在不需要字符串、对象的时候,需要释放其所占用的内存,否则将会消耗完系统中所有可用的内存,造成系统崩溃,这就是垃圾回收机制所存在的意义。
所谓的内存泄漏指的是:由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。
垃圾回收
javascript不同于c、c++的一个特点是:具有自动的垃圾回收机制,内存的分配以及内存的回收完全实现了自动管理,减少了许多不必要的麻烦。
所谓的垃圾回收就是找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。
全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收。
实现垃圾回收的方式主要有两种:标记清除,.引用计数
1.标记清除
目前最常用的垃圾回收的方式,也是当前浏览器所采用的垃圾收集策略。
原理:
在标记清除的方式中有两个概念:『进入环境』和『离开环境』。『进入环境』指变量进入执行的环境。『离开环境』指变量完成任务,离开了执行的环境。
当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
流程:
- 垃圾收集器在运行的时候给存储在内存中的所有变量都加上标记
- 去掉环境中的变量以及被环境中的变量引用的变量的标记
- 那些还存在标记的变量则被视为准备删除的变量。
- 最后垃圾收集器会执行内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间
2.引用计数
原理:
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。
相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。
这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。
不过这里潜在的一个问题是:循环引用时,两个对象都至少被引用了一次,将不能自动被回收。所以导致,我们常讲的内存泄露。所以这种机制在js中并不常用。
『循环引用』指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的引用。对于像js类的自动回收机制的语言来说,需要额外手动的去释放内存,其实并不友好,例如,在下面的例子中:
function ftc(){
var A = new Object();
var B = new Object();
A.property = B;
B.property = A;
}
内存泄漏的原因
虽然js有垃圾回收机制,但我们在编写代码的时候,有些情况还是会造成内存泄漏,了解这些情况,并在编写程序的时候,注意避免,我们的程序会更加完善。
1.上文我们提到了全局变量不会被当成垃圾回收,我们在编码中有时会出现下面这种情况:
function foo() {
this.bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2
bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar
}
foo();
当我们使用默认绑定,this会指向全局,this.something也会创建一个全局变量,这一点可能很多人没有注意到。
解决方法:在函数内使用严格模式or细心一点
function foo() {
"use strict";
this.bar2 = "严格模式下this指向undefined";
bar = "报错";
}
foo();
2.当不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
// 定时器也没有清除
}
// node、someResource 存储了大量数据 无法回收
}, 1000);
解决方法: 在定时器完成工作的时候,手动清除定时器。
3.循环引用问题,上文有提到
4.没有清理DOM元素引用:
html:
<div id="test">
<span>1</span>
<span>2</span>
</div>
js:
var refA = document.getElementById("test");
document.body.removeChild(refA); // dom删除了
console.log(refA, "refA"); // 但是还存在引用 能console出整个div 没有被回收
refA = null;//解决方法
console.log(refA, "refA"); // 解除引用
5.console保存大量数据在内存中。过多的console,比如定时器的console会导致浏览器卡死。
如何避免内存泄漏
- 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
- 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
- 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。