阅读 176

堆栈内存和垃圾回收

一、前置知识

1、JavaScript是一种动态的、弱类型语言

  • 动态的,表示可以使用一个变量保存不同的数据类型
  • 弱类型,表明JavaScript引擎会自动计算出变量的数据类型

2、JavaScript数据类型

JavaScript八大数据类型:undefined、null、Boolean、Number、String、BigInt、Symbol、Object

其中前七种是基本数据类型,最后一种是引用数据类型

3、内存模型

JavaScript内存划分为三类:代码空间、栈空间、堆空间。

代码空间用来存放可执行代码、栈空间是用来存储执行上下文的、堆空间是用来存储对象的。

4、堆栈内存的内容

我们把八大类型的数据划分为两类,对象是一类、其它是一类。对象类型的数据值会被放到堆空间,这个数据值的引用会被放到栈空间;其他类型的数据值会被直接放到栈空间。

二、栈空间的垃圾回收

栈空间是用来存放执行上下文的。当函数进行到return时,栈中的ESP就要向下移,销毁这个栈顶的可执行上下文并指向下一个执行上下文,此时对象类型的引用其他类型的值就都被回收了。

对象类型的引用被清除了,那么存在于堆内存的对象呢?

三、对象垃圾回收的前置知识

1、对象垃圾的产生

就是ESP指针向下移的时候,对象没有了引用,于是这个对象变成了垃圾。

2、代际假说和分代收集

  • 大部分对象的生命周期很短,分配以后就很快变成要回收的垃圾;小部分对象生命周期很长,一经分配甚至存在于整个生命周期
  • 所以,V8把堆内存划分为新生代和老生代两个区域,分别用来存放短命对象和长命对象
  • 在新生代和老生代各自维护了一个垃圾回收器,新生代的叫副垃圾回收器,老生代的叫主垃圾回收器

3、垃圾回收的工作流程

  1. 标记。标记就是区分出活动对象和非活动对象
  2. 回收。就是回收非活动对象
  3. 内存整理(可选)。回收对象后,容易出现大量不连续的空间,称为内存碎片。内存整理就是整理这些内存碎片。

四、堆内存的垃圾回收

已经知道了代际假说和分代收集,以及垃圾回收的工作流程。现在分别看新生代和老生代是如何垃圾回收的。

新创建的对象如果比较小,则会被分配给新生代,如果比较大,会直接被分配给老生代。

1、新生代/副垃圾回收器/Scavenge算法

新生代中用 Scavenge 算法来处理。

  • 首先把新生区对半分,对象区域一半,另一半为空闲区域。

  • 新创建的比较小的对象会被加入到新生代的对象区域,当对象区域满的时候就会执行垃圾回收。

  • 垃圾回收做了三件事:

    1. 标记:区分出活动对象和非活动对象
    2. 回收:将存活对象从对象区域复制到空闲区域中
    3. 内存整理:复制到内存区域的时候会进行排序,即完成了内存整理
  • 最后把空闲区变为对象区域,对象区域变成空闲区域

对象晋升策略

由于进行了频繁的复制,所以新生代设置的空间不大,容易被存活的对象给占满。所以Scavenge算法进行的垃圾回收次数特别多,副垃圾回收器进行两次垃圾回收之后还没被清除的对象会被晋升到老生代。

2、老生代/主垃圾回收器/标记-清除算法/标记-整理算法/增量标记算法

从新生代晋升来的对象,或者新创建的大对象,会加入到老生代

标记-清除算法

  • 标记。从根元素进行递归遍历,能到达的对象称它为可达的。可达的对象就是活动的,不可达的就是垃圾数据。
  • 回收。把不可达的对象清除掉。随着回收次数越来越多,内存碎片越来越多。

内存整理/标记-整理算法

  • 标记。和标记清除算法一样。从根元素进行递归遍历,能到达的对象称它为可达的。可达的对象就是活动的,不可达的就是垃圾数据。
  • 让所有可达对象向一端移动靠在一块连续的内存里面。清除不可达的对象(垃圾对象)

3、全停顿/增量标记算法

JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿(Stop-The-World)。

新生代空间小,其全停顿的影响不大。

老生代空间大,垃圾回收的时候可能占用时间过久,为了解决这个问题,出现了增量标记算法

增量标记算法

让标记-清除算法的标记过程,分解成多个标记的过程,这些标记的过程和JavaScript的应用逻辑交替进行,直到标记阶段完成,然后再进行标记-清除/标记-整理算法。

五、总结

栈内存的垃圾随着ESP的移动而清除

堆内存被分为新生代和老生代

新生代使用副垃圾回收器/Scavenge算法。这个算法把新生代分为对象区域和空闲区域,新创建的对象若比较小能放得下,就会被放到对象区域,当对象区域块满的时候,就会使用对其中活动对象进行标记,然后复制它们到空闲区并有序排列在一起。最后进行翻转操作,就是说把有了拷贝过来的对象的空闲区变为对象区域,原来的对象区域直接变成空闲区

老生代使用的算法比较多一点,标记的时候有增量标记算法、回收的时候有标记-清除算法、内存整理的时候有标记整理算法。增量标记算法是为了缓解老生代的全停顿时间太长带来的主线程阻塞。标记清除就是找出垃圾和清除垃圾。标记整理算法就是把所有可达对象都向一侧移动,然后清除掉非端边界以外的对象,这个算法实现了回收和整理两个过程。

文章分类
前端
文章标签