JS引擎中垃圾回收机制

思考:

1.JavaScript的内存是如何管理的?

2.Chrom浏览器是如何进行垃圾回收的?

我们带着以上两个思考来阅读文章。


一、JS内存管理

MDN介绍:“JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让 JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。”

内存管理大致分为3个部分:

1.分配你所需要的内存空间,在创建变量时zi'd

2.使用分配到的内存空间进行读写操作等

3.不需要使用内存时,将其空间释放或者归还

JavaScript大致将数据类型分为两个类别,基本类型数据和引用类型数据:

  • 基本数据类型:在内存中占据固定的内存空间,值保存在栈空间中,直接可以通过值来访问这些。
  • 引用类型:由于引用类型值大小不固定,栈内存中存放地址中的的内存对象,通过引用(地址)来访问的。
var a=123 //给数值变量分配栈内存空间
var etf = 'ARK' // 给字符串也是分配栈内存空间
// 给对象及其值分配堆内存
var obj = {
    name:'TOM',
    age:13
}
// 给数组及其值分配内存(和对象一样)
var a = [1, null]
// 给函数分配堆内存
function sum(a,b){
    return a+b
}

栈内存中的基本数据类型可以通过操作系统直接处理,而堆内存中的引用数据类型,正是由于可以经常变化,大小不固定,因此需要JavaScript的引擎通过垃圾回收机制来处理。 

二、垃圾回收

大多数内存管理问题都在这个都在这个阶段,如何判断哪些内存不再需要使用了要进行回收呢?

普通的理解方法参考MND:“垃圾回收机制有两种方式,一种是引用计数垃圾收集,一种是标记-清除算法”

1.引用

垃圾回收算法主要依赖于引用的概念。在内存环境中,一个对象如果具有访问另一个对象的权限就称为引用,分为显示显示引用和隐式引用。一个Javascript对象具有对它原型的引用叫做隐式引用,对它的属性的引用称为显示引用。

引用计数垃圾收集:

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

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


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

o = 1;      // 现在,“这个对象”只有一个 o2 变量的引用了,“这个对象”的原始引用 o 已经没有

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();

容易造成内存泄漏的问题。 

2.标记-清除算法

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。这个算法里假定设置一个(root)的对象(全局对象)垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

很好地解决了循环引用问题,在上面的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收。第二个示例同样,一旦 div 和其事件处理无法从根获取到,他们将会被垃圾回收器回收。

// 可达
var name = 'Tom'
var obj = {
  arr: [1, 2, 3]
}
console.log(window.name) // Tom
console.log(window.obj) // { arr: [1, 2, 3] }
console.log(window.obj.arr) // [1, 2, 3]
console.log(window.obj.arr[2]) // 3

function fn () {
  var age = 22
}
// 不可达
console.log(window.age) // undefined

❌:那些无法从根对象查询到的对象都将被清除

尽管这是一个限制,但实践中我们很少会碰到类似的情况,所以开发者不太会去关心垃圾回收机制

以上这些理解往往是不够的,请继续往下学习:

三、V8垃圾回收机制

Javascript中的V8引擎被限制了内存使用((64位约1.4G/1464MB , 32位约0.7G/732MB)),根据不同的操作系统内存大小也会有所不同。

为什么限制V8内存大小呢?

  • V8最开始是为浏览器而设计的引擎并未考虑占用过多的内存空间
  • 由于被V8的垃圾回收机制所限制,这样会引起Javascript执行的线程被挂起会影响当前执行页面的性能

1. 分代回收

1.1 新生代内存回收

在64位操作系统下分配为32MB,因为新生代中变量的存活时间短,不太容易产生太大的内存压力,因此不够大也是可以理解的。

图中左边是正在使用的内存空间,右边是闲置的内存空间 。V8引擎进行垃圾回收时,会将左边空间中的对象进行检查,会将左边存活的活动对象复制到右边空间,并且将这些对象的内存有序地排列起来,然后将From-space中的非活动对象进行释放,完成之后将from-space和to-space进行互换,这样可以使得新生代中的这两块区域重复利用。如果是顺序放置的比较好处理。

 图中橙色方便是存活的内存对象,白色是未分配的内存空间,接下来将介绍一种算法来解决这个问题,Scavenge算法,适用于内存比较小的情况,下图是经过算法处理后,内存空间的排布。

介绍Scavenge算法:

Scavenge算法是一种copy算法,copy存活的对象到to-space中

1.2 老生代内存回收

新生代中的变量如果经过回收后依然一直存在,那么就会直接被放入老生代内存中, 只要是已经经历过一次Scanvenge算法回收的就可以晋升未老生代内存中的对象。老生代中就不适合用Scavenge算法了,在老生代中采用Mark-Sweep(标记-清除)和Mark-Compact(标记-整理)的策略来进行老生代内存回收。

标记阶段和清除阶段:

 首先它会遍历堆上的所有对象,分别对它们打上标记,然后在代码执行结束后,对使用过的变量取消标记。在清除阶段,就会把还有标记的进行整体清除,从而释放内存空间。

但是通过标记清除后,还是会存在内存碎片问题。内存碎片多了之后,如果要新来一个较大的内存对象需要存储,会造成影响对于通过标记清除产生的内存碎片,还是需要通过标记整理策略进行解决。

标记整理(Mark-Compact):

 每次清理完非活动对象,就会把剩下的活动对象,整理到内存的一侧,整理完成后,直接回收掉边界上的内存。

参考: 内存管理 - JavaScript | MDN

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值