Javascript内存泄漏

时间:2022-09-27 08:03:34

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);
Javascript内存泄漏

每一秒钟执行一次run函数,它会申请一个巨大的字符串,创建一个闭包来使用它,调用闭包,然后返回。闭包返回之后,它会被垃圾回收,因为没有什么引用str对象了,所以str也被回收。 但是如果我们在run里面持续调用闭包呢?

Javascript内存泄漏
var run = function () {
var str = new Array(1000000).join('*');
var logIt = function () {
console.log('interval');
};
setInterval(logIt, 100);
};
setInterval(run, 1000);
Javascript内存泄漏

每一秒钟run申请一个巨大的字符串,并且在内部调用logIt 10次,logIt持续运行,而str在它的词法作用域内,所以这可能构成内存泄漏。 幸运的是,JavaScript的引擎(至少在最新的Chrome)足够聪明,它能检测到logIt并没有使用到str,所以str不会被放到logIt的此法作用域环境,所以呢每次run结束后,str被GC是没有问题的。 太棒了,JavaScript能帮助我们防止内存泄漏,对吗?让我们来点更复杂的,把前两个例子合并起来。

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);
Javascript内存泄漏

打开Chrome开发者工具,切换到Memory View视图,然后点record。

Javascript内存泄漏

看起来每秒钟都会增加1M的内存使用,甚至点击垃圾回收按钮来强直GC也不起作用,所以看起来str泄漏了。 不过这个看起来不是和之前的一样的情形吗,str只被run和doSomethingWithStr引用了。一旦run运行结束,doSomeThingWithStr会被回收。run里唯一一个引起泄漏的便是第二个闭包:logIt,并且logIt根本没指向str。 所以,尽管没有任何代码指向str,它也没有被GC,为什么呢?

好吧,闭包的典型实现是每一个函数对象有一个类似字典的对象来代表它的词法环境。如果定义在run里的函数确实使用了str,即使str被一次又一次的赋值,它也能保证两者引用的是相同的对象。如果变量没有被任何闭包使用的话,V8引擎足够聪明来让变量在函数的词法环境之外,所以第一个示例没有发生内存泄漏。 但是一旦一个变量被任一个闭包使用了,它会在所有的闭包词法环境结束之后才被释放,这会导致内存泄漏。

Javascript内存泄漏

到这里,你能想到一个更智能的词法环境的实现来避免这个问题。每一个闭包用一个字典记录它真正使用的变量,字典里的变量应当是一个可变单元,能被多个闭包共享词法环境。根据我阅读ECMA V5文档,这个方法是的合法的。ECMA文档指出词法环境是纯粹的规范机制,而不需要与EcmaScript的实现保持一致。

我们在Meter碰到的问题看起来是这样:我们只是希望用一个新对象取代一个对象,并且原来的对象被释放。

Javascript内存泄漏
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);
Javascript内存泄漏

很显然someMethod是个闭包,repalceThing执行结束后,因为theThing还引用着someMethod,originalThing就不会被释放。

一旦了解了这些,解决这个问题就很简单了。总结一下:如果你有一个大的对象被一些闭包使用,但是不是每一个闭包使用。只要保证使用完后本地变量不再指向它。不幸的是,这些Bug很难察觉。如果Javascript能让你不用考虑就好了。

Javascript内存泄漏的更多相关文章

  1. 一个意想不到的Javascript内存泄漏

    原文:http://point.davidglasser.net/2013/06/27/surprising-javascript-memory-leak.html 本周我在Meter的同事追踪到了一 ...

  2. 关于JavaScript内存泄漏的质疑

    近几天看了些关于JavaScript内存管理的文章,相对于Java JVM的内存管理,显得简单些. 在学习的过程中,发现有不少网友谈到了循环引用,说循环引用会造成内存泄漏,垃圾回收器无法回收. 实际上 ...

  3. 介绍两个非常好用的Javascript内存泄漏检测工具

    内存泄漏对开发者来说一般很难检测因为它们是由一些大量代码中的意外的错误引起的,但它在系统内存不足前并不影响程序的功能.这就是为什么会有人在很长时间的测试期中收集应用程序性能指标来测试性能. 最简单的检 ...

  4. JavaScript学习总结(二十三)——JavaScript 内存泄漏教程

    参考教程:http://www.ruanyifeng.com/blog/2017/04/memory-leak.html 一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时 ...

  5. JavaScript内存泄漏知多少?

    垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中 ...

  6. JavaScript 内存泄漏教程

    一.什么是内存泄漏? 程序的运行需要内存.只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存. 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存.否则,内存占用越来 ...

  7. 了解 JavaScript 应用程序中的内存泄漏

    简介 当处理 JavaScript 这样的脚本语言时,很容易忘记每个对象.类.字符串.数字和方法都需要分配和保留内存.语言和运行时的垃圾回收器隐藏了内存分配和释放的具体细节. 许多功能无需考虑内存管理 ...

  8. JavaScript中的内存泄漏以及如何处理

    随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概 ...

  9. How Javascript works (Javascript工作原理) (三) 内存管理及如何处理 4 类常见的内存泄漏问题

    个人总结: 1.两种垃圾回收机制: 1)引用标记算法:如果检测到一个对象没有被引用了,就清除它. ***这种算法不能处理循环引用的情况*** 2)标记—清除算法:从根(全局变量)开始向后代变量检测,任 ...

随机推荐

  1. python处理中文(待补充)

    字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(en ...

  2. 不会JS中的OOP,你也太菜了吧!(第二篇)

    一.你必须知道的 1> 原型及原型链在继承中起到了关键的作用.所以你一定要理解他们.2> 不会JS中的OOP,你也太菜了吧!(第一篇) 二.继承的6种方法 1> 原型链继承 原型链继 ...

  3. easyui源码翻译1.32--Tabs(选项卡)

    前言 使用$.fn.tabs.defaults重写默认值对象.下载该插件翻译源码 选项卡显示一批面板.但在同一个时间只会显示一个面板.每个选项卡面板都有头标题和一些小的按钮工具菜单,包括关闭按钮和其他 ...

  4. UTF-8和UTF-8无BOM,一个会导致文件中中文变量无法匹配的bug

    昨晚用dom4j中的selectSingleNode解析xml,匹配节点. 发现匹配不到,但是确实存在该节点 将regex改为regex1后则可以匹配,也就是说文件中的"阿里旺旺" ...

  5. linux-arm 安装 dotnetcore

    X86或者X64 安装.net core runtime  可以参照   https://www.cnblogs.com/nnhy/p/netcore_centos.html#4122354 而   ...

  6. Shpinx在PHPCMS里的使用及配置

    现在可以用最新版的Sphinx版本 网址:http://sphinxsearch.com/downloads/release/ 我使用rpm方式: 下载RHEL/CentOS 6.x x86_64 R ...

  7. codeblocks: 使用动态链接库(pcre)的配置

    说明:在c/c++程序中使用动态链接库, 编译后需要相关的dll文件(如:libpcre-1.dll,libpcreposix-0.dll)才能正常的运行. 2014-06-27

  8. 层次softmax函数(hierarchical softmax)

    一.h-softmax 在面对label众多的分类问题时,fastText设计了一种hierarchical softmax函数.使其具有以下优势: (1)适合大型数据+高效的训练速度:能够训练模型“ ...

  9. HTML之marquee妙用

    下面简短几句代码就实现了电子相册自动轮播 <center><h1>网页电子相册</h1></center> <marquee scrollamou ...

  10. Spring面试,IoC和AOP的理解

    spring 的优点?1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实 ...