JavaScript内存管理

作为一门高级语言,JS并不像低级语言C/C++那样拥有对内存的完全掌控。JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。

正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题,但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况。

内存的生命周期

JS环境中分配的内存一般有如下生命周期:

  1. 内存分配:当我们申明变量、函数、对象的时候,系统会自动为他们分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数等
  3. 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存

内存分配的几个例子:


// 为变量分配内存
var i = 11;
var s = "ifcode";

// 为对象分配内存
var person = {
    age: 22,
    name: 'ifcode'
};

// 为函数分配内存
function sum(a, b) {
    return a + b;
}

垃圾回收算法

对垃圾回收算法来说,核心思想就是如何判断内存已经不再使用了。下面介绍两种常见浏览器的垃圾回收算法。

引用计数算法

熟悉C语言的同学的都明白,引用无非就是指向某一物体的指针。对不熟悉底层语言的同学来说,可简单将引用视为一个对象访问另一个对象的路径。(这里的对象是一个宽泛的概念,泛指JS环境中的实体)。

引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需了。

下面来看个例子:

// 创建一个对象person,他有两个指向属性age和name的引用
var person = {
    age: 22,
    name: 'ifcode'
};

person.name = null; // 虽然设置为null,但因为person对象还有指向name的引用,因此name不会回收

var p = person; 
person = 1;         //原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收

p = null;           //原person对象已经没有引用,很快会被回收

由上面可以看出,引用计数算法是个简单有效的算法。但它却存在一个致命的问题:循环引用。如果两个对象相互引用,尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄露。

function cycle() {
    var o1 = {};
    var o2 = {};
    o1.a = o2;
    o2.a = o1; 
    
    return "Cycle reference!"
}

cycle();

上面我们申明了一个cycle方程,其中包含两个相互引用的对象。在调用函数结束后,对象o1和o2实际上已离开函数范围,因此不再需要了。但根据引用计数的原则,他们之间的相互引用依然存在,因此这部分内存不会被回收,内存泄露不可避免了。

正是因为有这个严重的缺点,这个算法在现代浏览器中已经被下面要介绍的标记清除算法所取代了。但绝不可认为该问题已经不再存在了,因为还占有大量市场的IE6、IE7使用的正是这一算法。在需要照顾兼容性的时候,某些看起来非常普通的写法也可能造成意想不到的问题:

var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
};

上面这种JS写法再普通不过了,创建一个DOM元素并绑定一个点击事件。那么这里有什么问题呢?请注意,变量div有事件处理函数的引用,同时事件处理函数也有div的引用!(div变量可在函数内被访问)。一个循序引用出现了,按上面所讲的算法,该部分内存无可避免地泄露哦了。

现在你明白为啥前端程序员都讨厌IE6了吧?拥有超多BUG并依然占有大量市场的IE6是前端开发一生之敌!亲,没有买卖就没有杀害。为了让你身边的前端程序员活得健康一些,请今天就升级你的浏览器吧!

标记清除算法

上面说过,现代的浏览器已经不再使用引用计数算法了。现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

标记清除算法将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

从这个概念可以看出,无法触及的对象包含了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)。但反之未必成立。

根据这个概念,上面的例子可以正确被垃圾回收处理了。当div与其时间处理函数不能再从全局对象出发触及的时候,垃圾回收器就会标记并回收这两个对象。

如何写出对内存管理友好的JS代码?

如果还需要兼容老旧浏览器,那么就需要注意代码中的循环引用问题。或者直接采用保证兼容性的库来帮助优化代码。

对现代浏览器来说,唯一要注意的就是明确切断需要回收的对象与根部的联系。有时候这种联系并不明显,且因为标记清除算法的强壮性,这个问题较少出现。最常见的内存泄露一般都与DOM元素绑定有关:

email.message = document.createElement(“div”);
displayList.appendChild(email.message);

// 稍后从displayList中清除DOM元素
displayList.removeAllChildren();

div元素已经从DOM树中清除,也就是说从DOM树的根部无法触及该div元素了。但是请注意,div元素同时也绑定了email对象。所以只要email对象还存在,该div元素将一直保存在内存中。

小结

如果你的引用只包含少量JS交互,那么内存管理不会对你造成太多困扰。一旦你开始构建中大规模的SPA或是服务器和桌面端的应用,那么就应当将内存泄露提上日程了。不要满足于写出能运行的程序,也不要认为机器的升级就能解决一切。当你从初级程序员走向资深的时候,关注细节真是你能脱颖而出的优点。

参考

Effectively Managing Memory at Gmail scale

Static Memory Javascript with Object Pools

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 第一章 简介 J2SE平台的一大优势是它的自动化内存管理,避免了开发者去面对内存管理的复杂性。 本文以Sun J2...
    tianyiliusha阅读 945评论 0 1
  • 内存声明周期 分配你所需要的内存 使用分配的内存(读写) 不再需要时释放内存 内存分配 javascript在声明...
    言叶丶阅读 203评论 0 0
  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,391评论 17 311
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,184评论 11 349
  • 目录页:我的algs4之旅 Union-Find是Algorithms, Part I第一周的第二部分。该部分的编...
    懂时已不是当时阅读 1,320评论 0 0