一、内存
1、内存概述
在硬件层面,计算机内存是由大量触发电路组成的。每个触发电路包含了一些晶体管并且能够存储一个位(bit)。
单个触发器可通过唯一的标识符寻址,因此我们可以读取并重写它们。
在概念上,我们可以将整个计算机内存看作是我们可以读和写的一个由位组成的大数组。
很多东西都存储在内存中:
1、所有程序使用的所有变量和数据;
2、程序的代码,包括操作系统的。
静态和动态内存分配:
2、内存生命周期
每种编程语言的内存生命周期都是差不多的:分配内存 => 使用内存 => 释放内存
“使用内存”在所有语言中都是显性的,而“分配内存”和“释放内存”在低级语言中是明确的,在 JavaScript 等高级语言中大多是隐性的。
二、JavaScript 内存分配概述
1、概述
像 C 语言,拥有底层原始的内存管理方法,如 malloc() 、free()。
而 JavaScript,在创建(对象、字符串等)时分配内存,并且在不再使用时自动释放。后面的过程被称为垃圾回收。
这种自动的垃圾回收就是管理混乱的根源,并造成了 JavaScript (其他高级语言)开发者不关心内存管理的错误现象。
即使是高级语言,自动内存管理也会遇到问题——垃圾回收中的错误或性能等问题,因此,我们需要对内存管理有一定的了解,以便处理内存管理中遇到的问题。
2、JavaScript 的回收机制
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。
由于事实上发现内存“不再被需要”是不可判定的,因此垃圾收集的通常解决方案都存在局限性。
用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。
1)引用计数
这是最简单的垃圾回收算法。如果一个对象指向它的引用数为 0,那么它就应该被“垃圾回收”了。
var o1 = {
o2: {
x: 1
}
};
// 2 objects are created. // 'o2' is referenced by 'o1' object as one of its properties.// None can be garbage-collected
var o3 = o1; // the 'o3' variable is the second thing that
// has a reference to the object pointed by 'o1'.
o1 = 1; // now, the object that was originally in 'o1' has a
// single reference, embodied by the 'o3' variable
var o4 = o3.o2; // reference to 'o2' property of the object.
// This object has now 2 references: one as
// a property.
// The other as the 'o4' variable
o3 = '374'; // The object that was originally in 'o1' has now zero
// references to it.
// It can be garbage-collected.
// However, what was its 'o2' property is still
// referenced by the 'o4' variable, so it cannot be
// freed.
o4 = null; // what was the 'o2' property of the object originally in
// 'o1' has zero references to it.
// It can be garbage collected.
但出现循环依赖就会产生限制。
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f();
上面代码中,两个对象彼此引用,创建了一个循环。
在函数调用之后,它们离开了作用域,因此它们实际上已经无用了,可以被释放了。然而,引用计数算法认为,由于两个对象中的每一个至少被引用了一次,所以也不能被垃圾回收。
2)标记扫描算法
为了确定一个对象是否被需要,这个算法会确定对象是否可以访问。
该算法由以下步骤组成:
垃圾回收器构建“roots”列表。Roots 通常是代码中保留引用的全局变量。在 JavaScript 中,“window” 对象可以作为 root 全局变量示例。
所有的 roots 被检查并标记为 active(即不是垃圾)。所有的 children 也被递归检查。从 root 能够到达的一切都不被认为是垃圾。
所有为被标记为 active 的内存可以被认为是垃圾了。收集器限制可以释放这些内存并将其返回到操作系统。
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f();
即使在循环情况下,在函数调用后,两个对象不再被从全局对象可访问的东西所引用。因此,垃圾回收器将发现它们是不可达的。
3、常见的 JavaScript 内存泄漏
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
1)全局变量
JavaScript 正常模式下,对未声明的变量的引用在全局对象内创建一个新变量。即:
function foo(arg) {
bar = "some text";
}
等价于
function foo(arg) {
window.bar = "some text";
}
如果 bar 本应是函数 foo 内的局部变量,而忘记声明,就会意外地创建一个全局变量。这就产生了内存泄漏。
下面代码也会创建意外的全局变量:
function foo() {
this.var1 = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)// rather than being undefined.
foo();
为防止上述两种创建意外全局变量的可能性,可以使用严格模式,在 JavaScript 代码开头添加“use strict”。严格模式中在上面两种情况下会抛出错误。
由于全局无法自动回收,除非将其赋值为 null 或重新进行分配。
那么在我们使用全局变量时要注意,尤其是用来临时存储和处理大量信息的全局变量非常值得关注。
如果你必须使用全局变量来存储大量数据,请确保在使用完后,对其赋值为 null 或重新分配。
2)闭包
闭包可以维持函数内局部变量,使其得不到释放。 上例定义事件回调时,由于是函数内定义函数,并且内部函数--事件回调的引用外暴了,形成了闭包:
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
}
解决之道,将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用:
//将事件处理函数定义在外部
function onclickHandler(){
//do something
}
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=onclickHandler;
}
//在定义事件处理函数的外部函数中,删除对dom的引用
function bindEvent(){
var obj=document.createElement("XXX");
obj.onclick=function(){
//Even if it's a empty function
}
obj=null;
}
3) 被遗忘的定时器或回调
var serverData = loadData();
setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); //This will be executed every ~5 seconds.
如上,定时器可能会引用不再需要的节点或数据,若 renderer 在将来被移除,整个定时器模块都不再被需要,但 interval handler 因为 interval 的存货,所以无法被回收(要停止 interva,才能回收)。因此 serverData 可能存储了大量数据,不能被回收。
那么我们就应在他们不再被需要的时候显示地删除它们(或让相关对象变为不可达)。
var element = document.getElementById('launch-button');
var counter = 0;
function onClick(event) {
counter++;
element.innerHtml = 'text ' + counter;
}
element.addEventListener('click', onClick);
// Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers // that don't handle cycles well.
如今,现代浏览器(包括 IE 和 Edge)都使用的是现代垃圾回收算法,可以检测这些循环依赖并正确的处理它们。换句话说,让一个节点不可达,可以不必而在调用 removeEventListener。
框架和库,例如 jQuery ,在处理掉节点之前会删除 listeners (使用它们特定的 API)。这些由库的内部进了处理,确保泄漏不会发生。即使是在有问题的浏览器下运行,如。。。。IE6。
4、怎样避免内存泄漏
1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
2)注意程序逻辑,避免“死循环”之类的 ;
3)避免创建过多的对象 原则:不用了的东西要及时归还。
参考:
https://blog.csdn.net/michael8512/article/details/77888000
https://blog.csdn.net/tangxiaolang101/article/details/78113871
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
网友评论