内存泄漏相关问题

来源:互联网 发布:nginx 子域名映射 编辑:程序博客网 时间:2024/06/06 04:42

转自博客:http://www.cnblogs.com/aaronjs/p/3370176.html;

http://www.cnblogs.com/snowwhite/p/6067571.html;


什么是内存泄漏

本质上来讲,内存泄露是当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或者空闲内存池的现象。编程语言使用不同的方式来管理内存。这些方式可能会减少内存泄露的机会。然而,某一块具体的内存是否被使用实际上是一个不可判定问题(undecidable problem)。换句话说,只有开发者可以搞清楚一块内存是否应该被操作系统回收。某些编程语言提供了帮助开发者来处理这件事情的特性。而其它的编程语言需要开发者明确知道内存的使用情况。维基百科上有几篇写的不错的讲述手动 和自动内存管理的文章。

总结:在对象/变量/函数声明的时候,就是向系统申请一块内存,来存储函数/变量/对象;

          内存泄露:一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束;

         浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露;

Javascript 的内存管理

Javascript 是那些被称作垃圾回收语言当中的一员。垃圾回收语言通过周期性地检查那些之前被分配出去的内存是否可以从应用的其他部分访问来帮助开发者管理内存。换句话说,垃圾回收语言将内存管理的问题从“什么样的内存是仍然被使用的?”简化成为“什么样的内存仍然可以从应用程序的其他部分访问?”。两者的区别是细微的,但是很重要:开发者只需要知道一块已分配的内存是否会在将来被使用,而不可访问的内存可以通过算法确定并标记以便返还给操作系统。



内存泄漏跟垃圾回收机制有很大的关系,接下来介绍导致内存泄漏几大情况:

1: 意外的全局变量

体现 JavaScript 宽容性的一点表现在它处理未声明变量的方式上:一个未声明变量的引用会在全局对象中创建一个新的变量。在浏览器的环境下,全局对象就是 window,也就是说:(未用var声明的)



全局变量的注意事项

尽管我们在讨论那些隐蔽的全局变量,但是也有很多代码被明确的全局变量污染的情况。按照定义来讲,这些都是不会被回收的变量(除非设置 null 或者被重新赋值)。特别需要注意的是那些被用来临时存储和处理一些大量的信息的全局变量。如果你必须使用全局变量来存储很多的数据,请确保在使用过后将它设置为 null 或者将它重新赋值。常见的和全局变量相关的引发内存消耗增长的原因就是缓存。缓存存储着可复用的数据。为了让这种做法更高效,必须为缓存的容量规定一个上界。由于缓存不能被及时回收的缘故,缓存无限制地增长会导致很高的内存消耗。


2: 被遗漏的定时器和回调函数

在 JavaScript 中 setInterval 的使用十分常见。其他的库也经常会提供观察者和其他需要回调的功能。这些库中的绝大部分都会关注一点,就是当它们本身的实例被销毁之前销毁所有指向回调的引用。在 setInterval 这种情况下,一般情况下的代码是这样的:

var someResource = getData();

setInterval(function() {

    var node = document.getElementById('Node');

    if(node) {

        // Do stuff with node and someResource.

        node.innerHTML = JSON.stringify(someResource));

    }

}, 1000);

定时器引用未被清除;


3: DOM 之外的引用

有些情况下将 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);

    // Much more logic

}

 

function removeButton() {

    // The button is a direct child of body.

    document.body.removeChild(document.getElementById('button'));

 

    // At this point, we still have a reference to #button in the global

    // elements dictionary. In other words, the button element is still in

    // memory and cannot be collected by the GC.

}

 

还需要考虑另一种情况,就是对 DOM 树子节点的引用。假设你在 JavaScript 代码中保留了一个表格中特定单元格(一个 <td> 标签)的引用。在将来你决定将这个表格从 DOM 中移除,但是仍旧保留这个单元格的引用。凭直觉,你可能会认为 GC 会回收除了这个单元格之外所有的东西,但是实际上这并不会发生:单元格是表格的一个子节点且所有子节点都保留着它们父节点的引用。换句话说,JavaScript 代码中对单元格的引用导致整个表格被保留在内存中。所以当你想要保留 DOM 元素的引用时,要仔细的考虑清除这一点。

总结:对象中的dom节点的引用未被移除;

4: 闭包

JavaScript 开发中一个重要的内容就是闭包,它是可以获取父级作用域的匿名函数。Meteor 的开发者发现在一种特殊情况下有可能会以一种很微妙的方式产生内存泄漏,这取决于 JavaScript 运行时的实现细节。

var theThing = null;

var replaceThing = function (){

  var originalThing = theThing;

  var unused = function (){

    if (originalThing)

      console.log("hi");

  };

  theThing = {

    longStr: new Array(1000000).join('*'),

    someMethod: function () {

      console.log(someMessage);

    }

  };

};

setInterval(replaceThing, 1000);

 

这段代码做了一件事:每次调用 replaceThing 时,theThing 都会得到新的包含一个大数组和新的闭包(someMethod)的对象。同时,没有用到的那个变量持有一个引用了 originalThingreplaceThing 调用之前的 theThing)闭包。哈,是不是已经有点晕了?关键的问题是每当在同一个父作用域下创建闭包作用域的时候,这个作用域是被共享的。在这种情况下,someMethod 的闭包作用域和 unused 的作用域是共享的。unused 持有一个 originalThing 的引用。尽管 unused 从来没有被使用过,someMethod 可以在 theThing 之外被访问。而且 someMethod 和 unused 共享了闭包作用域,即便 unused 从来都没有被使用过,它对 originalThing 的引用还是强制它保持活跃状态(阻止它被回收)。当这段代码重复运行时,将可以观察到内存消耗稳定地上涨,并且不会因为 GC 的存在而下降。本质上来讲,创建了一个闭包链表(根节点是 theThing 形式的变量),而且每个闭包作用域都持有一个对大数组的间接引用,这导致了一个巨大的内存泄露。

     总结:闭包不可滥用,闭包也是导致内存泄漏一个最大的问题;





原创粉丝点击