2018/2/2 19:04:32当前位置推荐好文程序员浏览文章

垃圾回收机制

自动垃圾收集机制

  • javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。即开发人员不需要当心内存使用的问题,所需内存的分配以及无用内存的回收完全实现了自动管理。这种垃圾回收机制的原理:找出那些不再继续使用的变量,然后释放其内存。为此,垃圾收集器会按照固定的时间间隔,周期性地执行这一操作。

局部变量的生命周期

  • 局部变量只在函数执行的过程中存在。在这个过程中,会为局部变量在栈内存或堆内存上分配相应的空间,以便存储他们的值。然后再函数中使用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放他们的内存以供将来使用。

javascript中的2中垃圾回收机制

标记清除(Mark-and-sweep)

  • 包括标记阶段和清除阶段。
  • 标记阶段:垃圾回收器创建了一个roots列表。Roots通常是代码中全局变量的引用。javascript中,window对象是一个全局变量,被当作rootwindow对象总是存在,因此垃圾回收器可以检查他和他所有子对象是否存在(即不是垃圾)。所有的roots被检查和标记为激活(即不是垃圾)。所有的子对象也被递归检查,检查从root开始的所有对象,如果是可达的,则打上标记。标记阶段到此结束。
  • 清除阶段:标记阶段完成时,被标记的对象被视为“存活”对象。然后重新进入递归扫描阶段,将没有标记的对象进行回收。这一阶段称为清除阶段。在扫描的同时,还需要将存活的对象的标记清除掉,以便下一次GC操作做准备。

引用计数(reference counting)

  • 引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则将该引用次数再加1。相反,如果包含对这个值得引用变量又取得了另一个值,则这个值的引用次数就减1。当这个值得引用次数变为0时,则说明没有办法在访问这个值了,因而久可以将其占用的内存空间回收回来。这样,垃圾回收器下次再运行时,会释放那些引用次数为0的值所占用的内存。

内存泄漏

  • 内存泄漏是指应用程序使用过的内存片段,在不需要的时候,不能返回到操作系统或可用的内存池中的情况。javascript中的内存泄漏同理,当一个值不再需要被引用了,但却无法被垃圾回收器回收的情况,就是内存泄露。

javascript中3种常见的内存泄漏

1. 意外的全局变量

举个例子(普通变量):

function fun(){  globalVar = "this is a hidden global variable";}//在函数体内部定义变量,如果没有使用var声明,则定义的变量是全局变量,即成为window的一个属性//而window总是存在,因此不会被回收

再举个例子(由this创建的变量):

function foo() {    this.variable = "potential accidental global";    console.log(this)}// Foo 调用自己,this 指向了全局对象(window)// 而不是 undefinedfoo();
image.png
  • 解决方案:在js文件头部加上(ES5)use strict,使用严格模式,可以避免此类错误的发生。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。

2.被遗忘的计数器或者回调函数

举个例子:

var someResource = getData();setInterval(function() {    var node = document.getElementById(Node);    if(node) {        // 处理 node 和 someResource        node.innerHTML = JSON.stringify(someResource));    }}, 1000);//重复计数器并没有使用 clearInterval()终止,导致内存无法回收
  • 对于观察者的例子,一旦它们不再需要(或者关联的对象变成不可达),明确地移除它们非常重要。老的 IE 6 是无法处理循环引用的。如今,即使没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是可以回收观察者处理函数的。

3. 脱离DOM的引用

  • 有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除
var elements = {    button: document.getElementById(button),    image: document.getElementById(image),    text: document.getElementById(text)};function doStuff() {    image.src = http://some.url/image;    button.click();    console.log(text.innerHTML);    // 更多逻辑}function removeButton() {    // 按钮是 body 的后代元素    document.body.removeChild(document.getElementById(button));    // 此时,仍旧存在一个全局的 #button 的引用    // elements 字典所引用的button 元素仍旧在内存中,不能被 GC 回收。}

4.闭包(正常情况下不会造成内存泄漏)

由于IE9 之前的版本对JScript 对象和COM 对象使用不同的垃圾收集。因此闭包在IE 的这些版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁

reference -- javascript高级程序设计第三版

举个例子:

function closure(){    var element = document.getElementById("someElement");    element.onclick = function(){        alert(element.id);    };}
  • 以上代码创建了一个作为element 元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对closure()的活动对象的引用,因此就会导致无法减少element 的引用数。只要匿名函数存在,element 的引用数至少也是1,因此它所占用的内存就永远不会被回收。

解决方案:把element.id 的一个副本保存在一个变量中,从而消除闭包中该变量的循环引用同时将element变量设为null。

function closure(){    var element = document.getElementById("someElement");    var id = element.id;    element.onclick = function(){        alert(id);    };    element = null;}
  • 因此可以看出,闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集,从而导致内存无法进行回收,这是IE的问题,所以正常使用闭包的情况下不会导致内存泄漏。

reference -- GC的三大基础算法

reference -- javascript内存泄露及其避免

闭包会造成内存泄漏吗?

网友评论