前端内存泄漏总结

988 阅读3分钟
 V8的垃圾回收机制分为新生代和老生代。

新生代主要使用Scavenge进行管理,主要实现是Cheney算法,将内存平均分为两块,使用空间叫From,闲置空间叫To,新对象都先分配到From空间中,在空间快要占满时将存活对象复制到To空间中,然后清空From的内存空间,此时,调换From空间和To空间,继续进行内存分配,当满足那两个条件时对象会从新生代晋升到老生代。 老生代主要采用Mark-Sweep和Mark-Compact算法,一个是标记清除,一个是标记整理。两者不同的地方是,Mark-Sweep在垃圾回收后会产生碎片内存,而Mark-Compact在清除前会进行一步整理,将存活对象向一侧移动,随后清空边界的另一侧内存,这样空闲的内存都是连续的,但是带来的问题就是速度会慢一些。在V8中,老生代是Mark-Sweep和Mark-Compact两者共同进行管理的。

标记-清除算法

由垃圾回收器维护一系列根节点(代码中被引用的全局变量)列表,在 JS 中,window 或 global 对象就可以看作是一个根结点。由于 window 对象是一直会存在的,所以它和其子对象都会被看作是一直可被追踪的,即这些对象对应的内存块不会被回收。 所有的根都会被检查并标记为引用状态,从根出发,递归地检查子结点。所有可被检查到的都视为非垃圾。 所有未被标记的内存块都被认为是垃圾内存,即回收器将回收这一块内存并返还给 OS 重新分配。

1.全局变量引起

function leaks(){  
    leak = 'xxxxxx';//leak 成为一个全局变量,不会被回收
}

2.闭包

var leaks = (function(){  
    var leak = 'xxxxxx';// 被闭包所引用,不会被回收
    return function(){
        console.log(leak);
    }
})()

3.dom清空,事件没有清除

var element = document.getElementById('button');
function onClick(event) {
    element.innerHtml = 'text';
}
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.

4.子元素存在引用

子元素还存在引用时,先把子元素释放再释放父元素,否则父元素无法释放。

被遗忘的计时器

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

如何避免内存泄漏

1.ESLint 检测代码检查非期望的全局变量。

2.使用闭包的时候,得知道闭包了什么对象,还有引用闭包的对象何时清除闭包。最好可以避免写出复杂的闭包,因为复杂的闭包引起的内存泄漏,如果没有打印内存快照的话,是很难看出来的。

3.绑定事件的时候,一定得在恰当的时候清除事件。在编写一个类的时候,推荐使用 init 函数对类的事件监听进行绑定和资源申请,然后 destroy 函数对事件和占用资源进行释放。