一个意想不到的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编码时,更好的应对内存泄漏带来的问题. 概 ...
随机推荐
- sql server 辅助工具
sql Prompt 智能感知, 也是需要注册机. 效果如图: 这里最好改下:
- OpenVPN多实例优化的思考过程
1.sss 当构建组件之间的关系已经错综复杂到接近于一张全然图的时候,就要换一个思路了,或者你须要重构整个系统,或者你将又一次实现一个. 2.TAP网卡和TUN网卡 2.1.TAP的优势 1.方便组网 ...
- Socket 学习(二)
1对1的发送和接收 Client 端 void ThreadReceive() { try { ...
- 网站静态化处理—web前端优化—上
网站静态化处理—web前端优化—上(11) 网站静态化处理这个系列马上就要结束了,今天我要讲讲本系列最后一个重要的主题web前端优化.在开始谈论本主题之前,我想问大家一个问题,网站静态化处理技术到底是 ...
- Appium Android Bootstrap源码分析之简介
在上一个系列中我们分析了UiAutomator的核心源码,对UiAutomator是怎么运行的原理有了根本的了解.今天我们会开始另外一个在安卓平台上基于UiAutomator的新起之秀--Appium ...
- WebService使用JSON格式传递笔记+JQuery测试
原文WebService使用JSON格式传递笔记+JQuery测试 因为一些因素,必须改写WebService,很传统,但是很多公司还在用.. 因为XML 的关系,不想让他传递数据的时候过度肥大,所以 ...
- linux 线程回顾
额,时隔两年重新写博客了. 这次看一下thread_cond_wait(pthread_cond_t * cond, pthread_mutex_t *mutex)和thread_cond_signa ...
- Python开发环境Wing IDE 5.0测试第八版发布
Wing IDE是著名的Python开发工具,是Wingware公司的主要产品.从1999年起,Wingware公司便开始专注于Python开发设计.Wing IDE在十几年的发展中,不管完善.其强大 ...
- 【SSRS】入门篇(四) -- 向报表添加数据
原文:[SSRS]入门篇(四) -- 向报表添加数据 定义好数据集后 [SSRS]入门篇(三) -- 为报表定义数据集 ,就可以开始设计报表了,将要显示在报表的字段.文本框.图像和其他项从工具箱拖放到 ...
- Array(数组)的使用
方法 说明 Concat() 连接2个或多个数组,并返回结果 Push() 向数组末尾添加一个或多个元素,并返回新的长度 Reverse() 颠倒数组中元素的顺序 Sort() 对数组的元素进行排序 ...