Javascript内存泄漏
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内存泄漏
原文:http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html 本周我在Meter的同事追踪到了一 ...
- 关于JavaScript内存泄漏的质疑
近几天看了些关于JavaScript内存管理的文章,相对于Java JVM的内存管理,显得简单些. 在学习的过程中,发现有不少网友谈到了循环引用,说循环引用会造成内存泄漏,垃圾回收器无法回收. 实际上 ...
- 介绍两个非常好用的Javascript内存泄漏检测工具
内存泄漏对开发者来说一般很难检测因为它们是由一些大量代码中的意外的错误引起的,但它在系统内存不足前并不影响程序的功能.这就是为什么会有人在很长时间的测试期中收集应用程序性能指标来测试性能. 最简单的检 ...
- JavaScript学习总结(二十三)——JavaScript 内存泄漏教程
参考教程:http://www.ruanyifeng.com/blog/2017/04/memory-leak.html 一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时 ...
- JavaScript内存泄漏知多少?
垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中 ...
- JavaScript 内存泄漏教程
一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来 ...
- 了解 JavaScript 应用程序中的内存泄漏
简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许多功能无需考虑内存管理 ...
- JavaScript中的内存泄漏以及如何处理
随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概 ...
- How Javascript works (Javascript工作原理) (三) 内存管理及如何处理 4 类常见的内存泄漏问题
个人总结: 1.两种垃圾回收机制: 1)引用标记算法:如果检测到一个对象没有被引用了,就清除它. ***这种算法不能处理循环引用的情况*** 2)标记—清除算法:从根(全局变量)开始向后代变量检测,任 ...
随机推荐
- CRUD功能的JqGrid表格
CRUD功能的JqGrid表格 之前的项目也曾用过JgGrid对它的基本功能也是略有了解,网上有个国外的开源的项目,但是不适合个人的风格,所以花了3天空余的时间封装了下JqGrid,也算是参加开发工作 ...
- WCF服务上应用protobuf
WCF服务上应用protobuf Web api 主要功能: 支持基于Http verb (GET, POST, PUT, DELETE)的CRUD (create, retrieve, updat ...
- javascript5
调用对象call object: 声明上下文对象declarative environment record; 作用域链scopechain: 变量解析:variable resolution: 引用 ...
- CSS学习笔记:溢出文本省略(text-overflow)
原文:CSS学习笔记:溢出文本省略(text-overflow) 在CSS3中,text-overflow属性的基本语法如下: clip:表示不显示省略文本,简单的裁切. ellipsis:表示对象文 ...
- 权限设计实现(MVC4+Bootstrap+ PetaPoco+Spring.Net)
权限设计实现(MVC4+Bootstrap+ PetaPoco+Spring.Net) 一.前言 至毕业后一直在做企业Web开发,做过的项目也有不少,每个项目的框架设计都不是一样,但是每个项目的权限模 ...
- Java多线程之进程和线程
在并发编程中有两个基本的概率就是进程和线程.在Java编程中并发编程更多的是关注线程.但是进程也是很重要的. 一个计算机一般会有很多活跃的进程和线程.有一点是没有疑问的在单核系统中,任何时候实际上都是 ...
- C语言字符串操作函数集
1)字符串操作 strcpy(p, p1) 复制字符串 strncpy(p, p1, n) 复制指定长度字符串 strcat(p, p1) 附加字符串 strncat(p, p1, n) 附加指定长度 ...
- [C++] 获取IE代理server的账号password
非常多程序须要使用'浏览器设置'的代理server,IE设置的代理server有可能是须要账号password的.如何编程获取浏览器设置的代理server的账号password呢? InternetQ ...
- 经历:asp.net oracle 部署问题以及解决方法
原文:[原创]经历:asp.net oracle 部署问题以及解决方法 精简的美丽...... 一.环境 开发环境 win7 64bit Vs2010 ...
- SSMS2008插件开发(4)--自定义菜单
原文:SSMS2008插件开发(4)--自定义菜单 打开上次的项目MySSMSAddin中的Connect类,发现该类继于了两个接口:IDTExtensibility2和IDTCommandTarge ...