Javascript垃圾回收机制


前言

JS自带一套内存管理引擎,负责创建对象、销毁对象,以及垃圾回收。这期探讨一下垃圾回收机制。垃圾回收机制主要是由一个叫垃圾收集器(garbage collector,简称GC)的后台进程负责监控、清理对象,并及时回收空闲内存。


一、明确目标

  1. JavaScript中的内存管理是自动的
  2. 对象不再被引用时是垃圾
  3. 对象不能从根上访问到时是垃圾

二、相关概念

(一)、可达性(Reachability)

可达对象
可以访问到的对象就是可达对象(引用、作用域链)
可达的标准是从根出发是否能够被找到
JavaScript中的根就可以理解为是全局变量对象


GC的最主要职责是监控数据的可达性(reachability);哪些数据是所谓的可达的呢?

所有显示调用,被称为根,包括

1. 全局对象

a. 正被调用的函数的局部变量和参数
b. 相关嵌套函数里的变量和参数
c. 其他(引擎内部调用的一些变量)

2. 所有从根引用或引用链访问的对象

(二)、内存泄漏

内存泄漏指申请的内存一直得不到释放,GC回收不了。一般在项目中就是,你创建的对象一直保存在内存中,可达但你把它的引用地址搞丢了结果没法操作它,而GC又不会回收这块内存。内存泄漏的危害就是堆积耗尽系统所有内存。

常见的泄露方式

a. 意外的全局变量
function foo() {
    bar = "等价于创建global变量window.bar";
}
b. 忘记清空计时器
let someResource = {...};
setInterval(function cb() {
    let node = document.getElementById('Node');
    if(node) {
        // 若不清空计时器,node和someResource将长期驻留内存
        node.innerHTML = JSON.stringify(someResource);
    }
}, 1000);
c. 闭包里的循环引用
function assignHandler(){
    let element = $('id');
    let id = elment.id; // 引用element变量id

    element.onclick = function(){
        alert(id); // 引用assignHandler变量id
    };
}
d. 其他

在IE等老旧浏览器里还有许多匪夷所思的内存泄漏,比如自动类型装箱转换,一些不经意的DOM操作,甚至闭包本身就会泄漏;这类泄漏需要专人特别关注,这里不再一一赘述了。

(三)、V8

概念

V8是一款主流的JavaScript执行引擎
V8采用即时编译
V8内存限制:64 位系统约为 1.4GB,32 位系统约为 0.7GB。
           这个说的是 V8 对使用内存的限制,而且是所有可用内存。
           当总使用内存超过 1.4G 之后就会 OOM(Out Of Memory)。

新生代、老生代

新生代主要存放的是哪些很快就会被GC回收掉的或者不是特别大的对象
老年代对象就是指存货时间较长的对象

三、GC算法介绍

(一)、GC概念

GC:Garbage Collection 垃圾收集
1960年 Lisp使用了GC
Java中,GC的对象是Java堆和方法区(即永久区)

我们接下来对上面的三句话进行一一的解释:

(1)GC:Garbage Collection 垃圾收集。这里所谓的垃圾指的是在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM。

在C/C++里是由程序猿自己去申请、管理和释放内存空间,因此没有GC的概念。而在Java中,后台专门有一个专门用于垃圾回收的线程来进行监控、扫描,自动将一些无用的内存进行释放,这就是垃圾收集的一个基本思想,目的在于防止由程序猿引入的人为的内存泄露。

(2)事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的3件事情:

哪些内存需要回收?
什么时候回收?
如何回收?

(3)内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作,每个栈帧中分配多少内存基本是在类结构确定下来时就已知的。在这几个区域不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。

而Java堆和方法区则不同,一个接口中的多个实现类需要的内存可能不同,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC关注的也是这部分内存,后面的文章中如果涉及到“内存”分配与回收也仅指着一部分内存。


(二)、GC算法

1. 概念

GC是一种机制,垃圾回收期完成具体的工作
工作的内容就是查找垃圾释放空间、回收空间
算法就是工作时查找和回收所遵循的规则

2. 常见GC算法

引用计数、标记清除、标记整理、分代回收

(1). 引用计数算法
原理

通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。

优点

发现垃圾时立即回收
最大限度减少程序暂停

缺点

无法回收循环引用的对象
时间开销大


(2). 标记清除算法
原理

当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

标记: Collector从引用根结点开始遍历,标记所有被引用的对象。
	  一般是在对象的Header中记录为可达对象。
清除: Collector对堆内存从头到尾进行线性的遍历,
	  如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
优点

可以解决引用计数无法回收循环引用的对象的问题

缺点

效率不算高
在进行GC的时候,需要停止整个应用程序,导致用户体验差
这种方式清理出来的空闲内存是不连续的,产生内存碎片。需要维护一个空闲列表


(3). 标记整理算法
原理

“标记 - 整理”算法的标记过程与“标记 - 清除”算法相同,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

优点

减少碎片化空间

缺点

不会立即回收垃圾对象
移动对象位置,回收效率慢


(4). 分代回收

主要用于V8,下面会继续介绍


四、V8回收策略

概述

  1. 采用分代回收思想

  2. 内存分为新生代、老生代。如图所示
    内存分配

  3. 针对不同对象采用不同算法

(一)、 V8垃圾回收策略图示

在这里插入图片描述

(二)、 V8垃圾回收常用算法

分代回收、空间复制、标记清除、标记整理、标记增量

(三)、 V8新生代的垃圾回收机制

1. 新生代内存分配

将新生代空间一份为二,暂且命名为From空间、To空间。在64位操作系统下分配32M,32位操作系统下分配16M
在这里插入图片描述

2. 新生代对象回收实现

回收过程中采用复制算法 + 标记整理
1. 将内存一分为二(From、To),每次只使用其中一块。
2. 发生垃圾回收时,将From中存活的对象复制到另一块未使用的内存(即To)
3. 清空使用的内存块中的对象,两者角色互换,完成垃圾回收。

3. 新生代对象回收细节说明

拷贝过程中可能出现晋升
晋升就是将新生代对象移动至老年代
需要晋升的对象判断标准:
	a. 一轮GC还存活的新生代对象需要晋升
	b. To空间的使用率超过25%

(四)、 V8老生代的垃圾回收机制

1. 老生代内存分配

老年代对象存放在右侧老生代区域
64位操作系统分配空间为1.4G,32位操作系统分配空间为700M

在这里插入图片描述

2. 老年代对象回收实现

主要采用标记清除、标记整理、增量标记算法
a. 首先使用标记清除完成垃圾空间的回收
b. 采用标记整理进行空间优化
	当我们把新生代对象往老生代区域移动时,但出现空间不足的情况时,
	就会采用标记整理算法。
c. 采用增量标记进行效率优化

4. 增量标记算法

如下图所示。

增量标记和程序执行是阻塞执行的。
将垃圾回收和程序执行交替执行。
首先遍历老年代对象进行标记,
然后对需要回收的对象进行再次标记(这一步为增量标记),
最后完成清除

在这里插入图片描述

3. 细节对比

新生代区域垃圾回收使用空间换时间
老生代区域垃圾回收不适合复制算法

(五)、 V8垃圾回收算法总结

新生代:复制+标记整理
老生代:标记清除、标记整理、增量标记


部分内容参考下列文章:
Javascript垃圾回收机制
GC算法详解
标记-清除算法原理及优缺点

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值