一个意想不到的Javascript内存泄漏
原文:http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html
本周我在Meter的同事追踪到了一个奇怪的Javascript内存泄漏。我找遍了互联网,尝试了各种关键字:javascript closure memory leak,无果。所以,这可能是一个未知的问题。(你们所找到的都是讲老版本的IE的垃圾回收算法的问题,但是我碰到的这个问题甚至影响到我当前装的最新Chrome浏览器。)
Update:Vyacheslav Egorov向我指出他曾经写过的一篇同样主题的文章,这篇文章有更详细的样例,更好的配图,加上一些V8内部机制的知识,很棒的一篇文章。甚至,还有一篇论文专门来讲这个问题的最佳实现方式,但是这篇文章并不是用Javascipt来说明的。
考虑如下代码:
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对象了,所以str也被回收。 但是如果我们在run里面持续调用闭包呢?
var run = function () {
var str = new Array(1000000).join('*');
var logIt = function () {
console.log('interval');
};
setInterval(logIt, 100);
};
setInterval(run, 1000);
每一秒钟run申请一个巨大的字符串,并且在内部调用logIt 10次,logIt持续运行,而str在它的词法作用域内,所以这可能构成内存泄漏。 幸运的是,JavaScript的引擎(至少在最新的Chrome)足够聪明,它能检测到logIt并没有使用到str,所以str不会被放到logIt的词法作用域环境,所以呢每次run结束后,str被GC是没有问题的。 太棒了,JavaScript能帮助我们防止内存泄漏,对吗?让我们来点更复杂的,把前两个例子合并起来。
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开发者工具,切换到Memory View视图,然后点record。

看起来每秒钟都会增加1M的内存使用,甚至点击垃圾回收按钮来强直GC也不起作用,所以看起来str泄漏了。 不过这个看起来不是和之前的一样的情形吗,str只被run和doSomethingWithStr引用了。一旦run运行结束,doSomeThingWithStr会被回收。run里唯一一个引起泄漏的便是第二个闭包:logIt,并且logIt根本没指向str。 所以,尽管没有任何代码指向str,它也没有被GC,为什么呢?
好吧,闭包的典型实现是每一个函数对象有一个类似字典的对象来代表它的词法环境。如果定义在run里的函数确实使用了str,即使str被一次又一次的赋值,它也能保证两者引用的是相同的对象。如果变量没有被任何闭包使用的话,V8引擎足够聪明来让变量在函数的词法环境之外,所以第一个示例没有发生内存泄漏。 但是一旦一个变量被任一个闭包使用了,它会在所有的闭包词法环境结束之后才被释放,这会导致内存泄漏。

到这里,你能想到一个更智能的词法环境的实现来避免这个问题。每一个闭包用一个字典记录它真正使用的变量,字典里的变量应当是一个可变单元,能被多个闭包共享词法环境。根据我阅读ECMA V5文档,这个方法是的合法的。ECMA文档指出词法环境是纯粹的规范机制,而不需要与EcmaScript的实现保持一致。
我们在Meter碰到的问题看起来是这样:我们只是希望用一个新对象取代一个对象,并且原来的对象被释放。
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
// 定义一个引用originalThing的闭包但是没有真正调用的
// 但是因为闭包的存在,originalThing会在词法环境中
//所有定义在replaceThing的闭包都能访问。
//你把这个函数去掉,就没有泄漏了。
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
// originalThing理论上会被这个someMethod使用
//虽然很明显这个函数没有使用它。
//但是因为originalThing是词法作用域的一部分,someMethod会保持对originalThing的引用。
//尽管我们每次调用都会把theThing覆盖但是原先的值永远不会被清除。
someMethod: function () {}
};
// 如果你在这里加上 `originalThing = null` 就不会泄漏了
};
setInterval(replaceThing, 1000);
很显然someMethod是个闭包,repalceThing执行结束后,因为theThing还引用着someMethod,originalThing就不会被释放。
一旦了解了这些,解决这个问题就很简单了。总结一下:如果你有一个大的对象被一些闭包使用,但是不是每一个闭包使用。只要保证使用完后本地变量不再指向它。不幸的是,这些Bug很难察觉。如果Javascript能让你不用考虑就好了。
一个意想不到的Javascript内存泄漏的更多相关文章
- Javascript内存泄漏
Javascript内存泄漏 原文:http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html 本周 ...
- 关于JavaScript内存泄漏的质疑
近几天看了些关于JavaScript内存管理的文章,相对于Java JVM的内存管理,显得简单些. 在学习的过程中,发现有不少网友谈到了循环引用,说循环引用会造成内存泄漏,垃圾回收器无法回收. 实际上 ...
- 介绍两个非常好用的Javascript内存泄漏检测工具
内存泄漏对开发者来说一般很难检测因为它们是由一些大量代码中的意外的错误引起的,但它在系统内存不足前并不影响程序的功能.这就是为什么会有人在很长时间的测试期中收集应用程序性能指标来测试性能. 最简单的检 ...
- JavaScript学习总结(二十三)——JavaScript 内存泄漏教程
参考教程:http://www.ruanyifeng.com/blog/2017/04/memory-leak.html 一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时 ...
- JavaScript内存泄漏知多少?
垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中 ...
- JavaScript 内存泄漏教程
一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来 ...
- c++:一个辅助类让内存泄漏现原形!
前言 对于c++而言,如何查找内存泄漏是程序员亘古不变的话题:解决之道可谓花样繁多.因为最近要用到QT写程序,摆在我面前的第一个重要问题是内存防泄漏.如果能找到一个简单而行之有效的方法,对后续开发大有 ...
- 了解 JavaScript 应用程序中的内存泄漏
简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许多功能无需考虑内存管理 ...
- JavaScript中的内存泄漏以及如何处理
随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概 ...
随机推荐
- FPGA 异步时钟处理方
1 假设FPGA设计,包括不同的频率的时钟,它会发出涉及异步时钟. 我们需要一些方法来使时钟同步,从而保证FPGA可靠性设计. 2 在建立和保持时间所构成的有效时间窗体内,数据输入到触发器进行转换. ...
- 记录近期小改Apriori至MapReduce上的心得
·背景 前一阵,一直在研究一些ML的东东,后来工作关系暂停了一阵.现在继续把剩下一些热门的算法再吃吃透,"无聊+逗比"地把他们搞到MapReduce上.这次选择的入手对象为Apri ...
- springmvc继承activemq(原创)
1.加入jar包 我项目是用maven构建的,所以这里就给大家贴一下配置了,具体jar大家可以参看下Activemq分类中的文章 <project xmlns="http://mave ...
- 使用.NET REACTOR制作软件许可证
原文:使用.NET REACTOR制作软件许可证 软件下载地址:http://www.eziriz.com/downloads.htm 做一个简单的许可证系统,下面是具体步骤: 1, OPEN AS ...
- Asp.Net MVC5入门学习系列⑦
原文:Asp.Net MVC5入门学习系列⑦ 接着上篇结尾所说,如果开发中刚才遇到Model需要添加或者减少字段/属性的话,但是刚好你也利用EF的Code frist通过Model生存的数据库,这时改 ...
- 图片alpha blending的计算
转载时请注明出处和作者联系方式:http://blog.csdn.net/mimepp作者联系方式:YU TAO <yut616 at sohu dot com> 一幅彩色图像的每一个像素 ...
- 递归与尾递归(C语言)
原文:递归与尾递归(C语言)[转] 作者:archimedes 出处:http://www.cnblogs.com/archimedes/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留 ...
- [Unity-7] Update和FixedUpdate
1.Update和FixedUpdate这是Unity既用内提供的帧功能接口相关联. Update():这个函数里面的内容每一帧都会被运行一次.这个函数有一个特点,那就是运行的频率等于帧率.而这个帧率 ...
- Math.Round函数四舍五入
Math.Round函数四舍五入的问题 今天客户跑过来跟我说,我们程序里面计算的价格不对,我检查了一下,发现价格是经过折算后的价格,结果是可能小数位较多,而单据上只能打印两位价格,所以就对价格调用 ...
- Django小例子 – 模型数据的模板呈现
学习Django的这几天,学习过程还是很愉快的,django采用的MVC架构,学习曲线十分平缓,在深入学习之前,先简单的整理记录下django从数据库中获取数据并在模板中使用的方法.温故而知新 ^_^ ...