每种语言都有垃圾回收机制,来回收不再使用的内存空间,垃圾回收又分为主动回收和被动回收两种...

在js,python,java中使用的都是自动垃圾回收机制,开发者无需主动清除变量所占用的内存,但是在某些场景下,关注内存情况还是很必要的。

js中的变量都存储在栈和堆中,二者回收机制也不同。

栈内存回收

js代码会放到一个执行栈中运行,每碰到一个运行函数的指令,就为当前函数创建执行上下文,然后推入调用栈,当函数执行完毕,则函数出栈,开始执行下一个任务。
js内部会有一个ESP(记录当前执行状态的指针),该指针总是指向当前的执行上下文,当函数执行完毕ESP指针下移之后,还会销毁ESP上一个指向的函数保存在栈中的执行上下文,从而自动回收其中保存在栈中基本类类型的内存。

堆内存回收

基本类型存在栈中的内存会随着执行上下文被销毁而自动回收,而堆内存需要额外的机制来进行回收。

代际假说(The Generational Hypothesis)

  • 大部分对象在内存中存活的时间很短,很多对象占用的内存一经分配就变得不可访问 (新生代)
  • 不死的对象会活得更久 (老生代)
    为了对这两种情况下的垃圾进行回收,在V8中会把堆内存分为新生代和老生代两个区域。
    新生代的容量较小,老生代容量大。然后针对这两种区域分别使用副回收器(新生代)主回收器(老生代)对垃圾进行回收。

副垃圾回收器

新生代和老生代
副垃圾回收器使用的是一种Scavenge算法来处理垃圾的,新生代会被对半划分为对象区域和空闲区域,新加入的对象会加入对象区域,当对象区域快被写满的时候就会开始垃圾回收。

  • 标记对象区域还在使用的对象
  • 将被标记的对象复制到空闲区域
  • 对复制后的对象做内存整理操作,合并内存间隙
  • 角色翻转,将对象区域转为空闲区域,对空闲区域转为对象区域,上一次未被标记的对象占用的内存则被垃圾回收
    如果新生代内存设置得过大,那么每次清理时间就会过久,造成程序运行缓慢,所以为了执行效率,一般新生代的空间会设的比较小,js中一般是1~8M。新生代的内存区域不大如果都放在新生代则容易被存活对象占满整个区域,所以js引擎采用了对象晋升策略,进过两次垃圾回收依然存活的对象,会被移动到老生代中。另外除了从新生区晋升的对象外,一些比较大的对象会直接被分配到老生代。

主垃圾回收器

主垃圾回收器负责回收老生代区域的垃圾对象,它有内存大,存活时间长等特点,如果继续采用Scavenge算法,拷贝大内存对象十分耗时,导致垃圾回收执行效率不高。因此,主垃圾回收器采用的是标记清除垃圾回收算法。
主垃圾回收器
主垃圾回收器会定期遍历当前的调用栈,然后对当前老生代中的对象使用情况进行标记,如果在调用栈发现了对对象的引用则标记为活动对象,否则标记为垃圾数据。
当对老生代的垃圾回收之后进行标记整理使所有对象向一端移动清除内存间隙。

增量标记算法(只针对老生代才有标记清除算法)

由于如果js运行的主线程是单线程作业,如果垃圾积攒过多,比如1.5G的老生代需要大约1s的时间进行回收,那么js运行会被垃圾回收阻塞,页面就会有明显的卡顿。因此,为了避免垃圾回收对程序运行带来的影响,V8将标记过程分为一个个的子标记过程,垃圾回收标记和js应用逻辑交替运行,这种算法叫增量标记算法,将一个完整的垃圾回收任务分为很多个小任务,用户就不会因为垃圾回收的阻塞而感到卡顿了。(但是其实这也是在刷题的时候感觉js运行效率不高的原因吧= =,在逻辑运算过程中还穿插了垃圾回收,向java这种多线程语言垃圾回收应该是单独一个线程吧猜想(¦3[▓▓] )。

总结

懒一下,之后总结。。。😹😹😹

资料参考

极客时间浏览器工作原理与实践(李兵)