零散专题34 JavaScript的垃圾回收机制

手动回收?

JavaScript在创建变量时自动进行了内存分配,并且在不使用时自动释放。释放的过程叫做垃圾回收。

JavaScript没有暴露任何垃圾回收期的接口,其内存管理是自动执行且不可见的,所以开发者没有办法手动进行强制的垃圾回收,也没有办法干预内存管理。

在多数情况下,开发者也没有必要手动解除对象的引用,只要简单地把变量放在他们应该的地方(局部变量),垃圾就能正确被回收

V8的内存分配

当声明变量并赋值时,V8就会在堆内存中分配一部分给这个变量。如果已申请的内存不足以存储这个变量,V8就会继续申请,直到堆的大小到了V8的内存上限为止。

堆内存分为两种分配方式:

  1. 静态分配,全局变量、函数之类的分配方式,它们在页面没有关闭之前,是不会被清除的
  2. 动态分配,使用new创建出来的,主动要求给分配空间的

可达性

JavaScript中内存管理关键的一个概念是可达性,一个变量的“可达性”值得就是它能够被以某种方式访问到或者引用到,被保证存储到内存中。JavaScript引擎中有一个名为“垃圾回收器”的进程,它监视所有对象,并删除不可访问的对象。

一些常见的具有可达性,会常驻内存的情况:

(1)全局变量,全局变量会常驻内存,除非刷新页面、离开页面时才会被清理

(2)对象引用

function ob(){
  var bar  = new largeObject(); // 很大一个对象\变量\字符串
  bar.someCall();
  return bar;
}

var a = new ob();

现在有一个指向bar对象的引用,当ob调用结束后,bar对象不会被回收,直到变量a分配其他引用(或者a超出了作用域范围)。

(3)DOM事件,即使DOM元素被移除,其绑定的事件不会被回收,除非使用removeEventListener显式的移除事件

(4)定时器,除非显式的清楚定时器

例子

假设定义了这样一个变量:

let user = {
  name: 'John'
}

user这个变量引用了一个对象{name: 'John'}

image

如果将user的值覆盖user = null,则引用丢失,{name: 'John'}会被垃圾回收器清除,分配的内存被回收。

image

如果不将user的值覆盖,而是把user复制给另一个变量admin

let admin = user

这是两个变量都引用了同一个对象{name: 'John'}

image

这个时候再将user的值覆盖user = null,虽然user不再引用{name: 'John'},但是admin仍然与{name: 'John'}保持引用关系,所以{name: 'John'}不会被垃圾回收器清除,分配的内存也不会被回收。

复杂些的例子

函数marry通过给两个对象彼此提供引用来“联姻”它们,并返回一个包含两个对象的新对象

function marry (man, woman) {
  woman.husban = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
})

产生的内存结构:

image

现在如果想要清除{name: John}这个变量,必须要同时删除外界对它的所有引用

delete family.father;
delete family.mother.husband;
image

它对外的引用man.wife是否删除不重要,因为只有外界对变量的引用,才决定了这个变量是否是“可达”的。现在虽然{name: John}仍然通过wife属性引用了{name: 'Ann'},但是外界对它的引用都删除了,所以它是不可达的,所以会被垃圾回收器清除。

image

也有可能整个对象变的不可访问从而被从内存中删除,例如我们切断family的引用family = null,那么:

image

虽然{name: John}{name: 'Ann'}仍然相互引用,但是family已经从根上断开了连接,不再有对它们的外界的引用(也就是说无法从局部或者全局变量访问到它们),所以下面整个块都是不可达的,整体都会被删除。

垃圾回收的算法

标记-清除算法

垃圾回收的基本算法是“标记-清除”,具体步骤如下,会被定期执行:

(1)垃圾回收器获取「根」并标记

image

(2)继续访问并标记所有来自「根」的引用,所有标记过引用都不会被重复访问:

image

(3)继续标记引用的后代引用,直到所有的“可达”(可以从根访问的)的引用全部被标记为止

image

(4)删除所有未标记的对象,回收它们占用的内存:

image

这就是JavaScript垃圾回收期的工作原理,JavaScript引擎应用了许多优化,使其运行的更快,并且不影响执行,例如:

(1)分代回收

对象分为两组“老对象”和“新对象”,许多新对象出现,完成任务后迅速被解除应用,它们很快被清理。而那些存活时间足够久的对象就会认为是“老对象”,很少接受检查

(2)增量回收

如果试图一次性遍历并标记整个对象集,那么花费时间会很长,因此引擎会试图将垃圾回收分解为多个部分,然后各个部分分别执行。这需要额外的标记来跟踪变化

(3)空闲时间收集

垃圾回收期只在CPU空闲时运行,以减少对正常任务的影响

除了“标记-清除”算法之外,还有其他的多种算法,比如“引用计数”算法,记录每个对象被引用的次数,每次新建对象、赋值引用和删除引用的同时更新计数器,如果计数器值为0则直接清除回收内存。这篇文章介绍了多种算法,并分析了优缺点,写的很好。

引用计数算法

引用计数算法是最初级的垃圾收集算法,此算法把“对象是否不再需要”简化定义为“有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收器回收。

var o = { 
  a: {
    b:2
  }
}; 
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集

var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1;      // 现在,“这个对象”的原始引用o被o2替换了

var oa = o2.a; // 引用“这个对象”的a属性
               // 现在,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = "yo"; // 最初的对象现在已经是零引用了
           // 他可以被垃圾回收了
           // 然而它的属性a的对象还在被oa引用,所以还不能回收

oa = null; // a属性的那个对象现在也是零引用了
           // 它可以被垃圾回收了

这种算法没有办法处理循环引用的情况,例如下面的例子,两个对象被创建,并相互引用,形成循环。它们被调用后不再被外部引用,没用了,可以被回收了。但是引用计数算法认为它们都还至少有一次引用,所以不会被回收,一直保存在内存中。

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

经验法则

为了使Chrome的垃圾回收器不保留不再需要的对象,有几点需要牢记:

  1. 在恰当的作用域中使用变量,尽量在函数作用域中声明变量,尽量声明局部变量,尽量避免全局变量
  2. 确保移除不再需要的事件监听器,比如即将被移除的DOM对象所绑定的事件
  3. 避免缓存大量不会被重用的数据
  4. 少用闭包
  5. 不要再生产环境使用console.log()console.error()console.dir()等方法打印任何复杂对象,因为这些对象不会被垃圾回收器回收。

参考

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

推荐阅读更多精彩内容