replaceThing 闭包泄漏(A surprising JavaScript memory leak found at Meteor)

JavaScript是一种隐蔽的功能性编程语言,其函数是闭包(封闭):函数对象可以访问其封闭作用域中定义的变量,即使该作用域已经完成。 一旦它们定义的函数已经完成,并且在其作用域内定义的所有函数本身都被 GCed(垃圾收集),那么由闭包捕获的局部变量就被垃圾收集。

var run = function () {
  var str = new Array(1000000).join(‘*‘);
  var doSomethingWithStr = function () {
    if (str === ‘something‘)
      console.log("str was something");
  };
  doSomethingWithStr();
};
setInterval(run, 1000);

我们将每秒执行一次run函数。它将分配一个巨大的字符串,创建一个使用它的闭包,调用闭包并返回。返回后,闭包可以被垃圾收集,str也可以,因为没有什么引用它。但是,如果我们有一个闭包比run久的呢?

var run = function () {
  var str = new Array(1000000).join(‘*‘);
  var logIt = function () {
    console.log(‘interval‘);
  };
  setInterval(logIt, 100);
};
setInterval(run, 1000);

每隔一秒run 分配一个巨大的字符串,并开始每隔100微秒记录日志一次。logIt 永远都会持续,str 在其词法作用域内,所以这可能造成内存泄漏!幸运的是,JavaScript实现(或至少是现在的Chrome)足够聪明可以注意到在logIt中没有使用str,所以它不会被放在logIt的词法环境中,而且一旦运行完成,大字符串会被垃圾回收。

var run = function () {
  var str = new Array(1000000).join(‘*‘);
  var doSomethingWithStr = function () {
    if (str === ‘something‘)
      console.log("str was something");
  };
  doSomethingWithStr();
  var logIt = function () {
    console.log(‘interval‘);
  }
  setInterval(logIt, 100);
};
setInterval(run, 1000);

在Chrome开发者工具中打开“时间轴”选项卡,切换到内存视图,并点击记录:

看起来我们每秒多用额外的兆字节。甚至点击垃圾桶图标手动清理垃圾回收也没有帮助,所以看起来正在泄漏str。(译注:测试了新版chrome 59.0.3071.115(正式版本)这里好像没有出现泄漏

但是这不是和以前一样吗?str  仅在run函数体中引用,在doSomethingWithStr 函数中引用。一旦run 结束  doSomethingWithStr 本身就被清理掉。唯一从run 中泄漏的是第二个闭包,logIt . 而 logIt 根本没引用str!

所以即使没有任何代码再次引用str,它也不会被垃圾回收器回收。为什么?典型的闭包实现是每个函数对象有一个链接到一个表示它词法作用域的字典类型对象。如果定义在run 函数中的两个函数的确用到str,重要是即使str被分配了一次又一次,这两个函数共享相同的词法环境。现在,Chrome的V8 javascript引擎中,如果变量(如例子中的字符串)没有被闭包引用时,可以将变量保留在词法环境之外,这就是第一个例子没有泄漏的原因。

但是只要变量被任何闭包使用,它将出现在在该作用域所有闭包共享的词法环境中。

你可以想象一个更聪明的词法环境实现来避免这个问题。每个闭包可以有一个读取和写入包含变量的字典(译注:对象);该字典中的值是可以 在多个闭包的词法环境中共享的变异元()。基于我对ECMAScript第5版标准的随意阅读,这是合法的:它对词汇环境的描述将其描述为“纯规范机制(不需要对应于ECMAScript实现的任何具体的文件)”。也就是说,这个标准实际上并不包含“垃圾”一词,只能说“内存”一次。

一旦你注意到这种形式的内存泄漏,修复它们是直接的,如修复Meteor 缺陷中演示的(the fix to the Meteor bug.)。在上面的例子中,很明显,我们有意泄漏logIt 而不是str。在原始的Meteor bug,我们不打算泄漏任何东西:我们只想用一个新对象代替一个对象,且允许先前的版本被释放,如下所示:

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  // Define a closure that references originalThing but doesn‘t ever actually get called.
//定义一个引用originalThing但实际上没有调用它的实例闭包
  // But because this closure exists, originalThing will be in the
  // lexical environment for all closures defined in replaceThing, instead of
  // being optimized out of it. If you remove this function, there is no leak.
//但是由于这个闭包的存在,originalThing将存于定义在replaceThing中的所有闭包的词法环境中,而不被优化,如果你移除 这个方法,就不会有泄漏
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join(‘*‘),
    // While originalThing is theoretically accessible by this function, it
    // obviously doesn‘t use it. But because originalThing is part of the
    // lexical environment, someMethod will hold a reference to originalThing,
    // and so even though we are replacing theThing with something that has no
    // effective way to reference the old value of theThing, the old value
// will never get cleaned up!
//虽然originalThing理论上可以通过这函数(someMethod)访问,但显然没有使用它,但是由于originalThing是词法环境的一部分,someMethod将会保留一个引用指向originalThing,所以即使我们用非有效的方式替换旧的theThing值,但是旧值不会被清理。
    someMethod: function () {}
  };
  // If you add `originalThing = null` here, there is no leak.
//此处加originalThing = null不会有泄漏
};
setInterval(replaceThing, 1000);

总结一下:如果你有大对象被一些闭包使用, 而(译注:这些闭包)不是任何你需要继续使用的闭包,只要确保当你用大对象的时候,局部变量不再指向它。不幸的是,这些错误可能非常巧妙的(不好发现); 如果JavaScript引擎不需要您考虑它们会更好一些。

原文地址: http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html

相关: http://mrale.ph/blog/2012/09/23/grokking-v8-closures-for-fun.html

时间: 2024-10-26 16:33:40

replaceThing 闭包泄漏(A surprising JavaScript memory leak found at Meteor)的相关文章

Linux C/C++ Memory Leak Detection Tool

目录 1. 内存使用情况分析 2. 内存泄漏(memory leak) 3. Valgrind使用 1. 内存使用情况分析 0x1: 系统总内存的分析 可以从proc目录下的meminfo文件了解到当前系统内存的使用情况汇总,其中可用的物理内存 = memfree + buffers + cached当memfree不够时,内核会通过回写机制(pdflush线程)把cached和buffered内存回写到后备存储器,从而释放相关内存供进程使用,或者通过手动方式显式释放cache内存:echo 3

内存泄漏(memory leak)和内存溢出

1. 什么是内存泄漏(memory leak)? 指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况.内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费. 2. 两种类型的内存泄漏: 堆内存泄漏(Heap leak).对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉.如果程序的设计的错误导致这部分内存没有被释放,

JavaScript :memory leak [转]

Memory leak patterns in JavaScript Handling circular references in JavaScript applications Abhijeet Bhattacharya and Kiran Shivarama SundarPublished on April 24, 2007 FacebookTwitterLinked InGoogle+E-mail this page 6 JavaScript is a powerful scriptin

Memory Leak(内存泄漏)问题总结(转)

最近听了一些关于Memory Leak(内存泄漏)的seminar,感觉有些收获,所以留个记录,并share给朋友. 1 什么是Memory Leak. Memory Leak是指由于错误或不完备的代码造成一些声明的对象实例长期占有内存空间,不能回收.Memory Leak会造成系统性能下降,或造成系统错误. 2 Memory存储模式 我们通常写的C++或Java Code在内存里边的存储状况概如下图. 简单的说,一般局部变量存储于Stack中,以提高运行问速度.而New出来的变量则将引用信息或

安卓android WebView Memory Leak WebView内存泄漏

Android WebView Memory Leak WebView内存泄漏 在这次开发过程中,需要用到webview展示一些界面,但是加载的页面如果有很多图片就会发现内存占用暴涨,并且在退出该界面后,即使在包含该webview的Activity的destroy()方法中,使用webview.destroy();webview=null;对内存占回收用还是没有任何效果.有人说,一旦在你的xml布局中引用了webview甚至没有使用过,都会阻碍重新进入Application之后对内存的gc.包括

To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

1.错误描述 严重: The web application [/AMST] registered the JDBC driver [org.logicalcobwebs.proxool.ProxoolDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. 八

警告: The web application [ROOT] appears to have started a thread named [Thread-48] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

1. 问题描述 tomcat跑web项目(其中依赖java项目) 出现大量上述警告 项目起不来 关键字 memory leak 内存泄漏 2. 解决方案 难道是程序写的有问题? 最终 将tomcat VM参数中 内存调大 解决了 -Xms512m -Xmx512m -Xmn300m -Xss2048k -XX:PermSize=512m -XX:MaxPermSize=512m 需要根据机器的配置做相应调整

内存溢出(Oom)和内存泄露(Memory leak)

内存溢出(Oom):运行内存大于可用内存的情况.比如申请了一个integer空间,结果存放下了只有long才能存放的数据 内存泄露(Memory leak):程序员忘记释放已用内存的情况,是内存管理较为常见的现象 以发生的方式来分类,内存泄漏可以分为4类: 1. 常发性内存泄漏.发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏. 2. 偶发性内存泄漏.发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生.常发性和偶发性是相对的.对于特定的环境,偶发性的也许就变成了常发性

Android 内存管理 &Memory Leak & OOM 分析

1.Android 进程管理&内存 Android主要应用在嵌入式设备当中,而嵌入式设备由于一些众所周知的条件限制,通常都不会有很高的配置,特别是内存是比较有限的.如果我们编写的代 码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机.为了能够使得Android应用程序安全且快速的运行,Android 的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程演变过来的,也就是说每个应用程序都是在属于自己的进程中运行的.一方面,如果程序在运行