
本文共 1533 字,大约阅读时间需要 5 分钟。
调用栈中的数据是如何回收的
首先是调用栈中的数据,我们通过一段示例代码的执行流程来分析其回收机制。考虑如下代码:
function foo() { var a = 1; var b = { name: "极客邦" }; function showName() { var c = 2; var d = { name: "极客时间" }; } showName();}foo();
当执行到第 6 行代码时,调用栈和堆空间状态如下:
从图中可以看出,原始类型的数据被分配到栈中,引用类型的数据会被分配到堆中。当 foo
函数执行结束之后,foo
函数的执行上下文会从堆中被销毁掉,那么它是怎么被销毁的呢?
当执行到 showName
函数时,JavaScript 引擎会创建 showName
函数的执行上下文,并将 showName
函数的执行上下文压入到调用栈中。 ESP 指针指向当前执行状态的地址。
当 showName
函数执行完成之后,JavaScript 会通过移动 ESP 来销毁 showName
函数执行上下文。上移或下移 ESP 呢?其实是下移。执行流程如下:
showName
函数执行结束- ESP 下移到
foo
函数的执行上下文
这样,showName
的执行上下文保存在栈中被销毁。ESP 作为运行时的内存管理器,通过指针的移动状态来决定对象的存活状态。
堆中的数据是如何回收的
当 foo
函数执行结束后,showName
和 foo
函数的执行上下文已经从堆中销毁,但堆中的对象仍然存在。例如,1003 和 1050 这两个对象虽然不再引用,但仍然存在内存中。如何回收这些堆中的垃圾数据呢?
答案是,JavaScript 垃圾回收器负责回收。
垃圾回收器的工作流程
垃圾回收过程可以分为以下几个步骤:
具体来说,Suffix垃圾回收器采用 Scavenge 算法,对新生代进行垃圾回收。
新生代垃圾回收器
新的垃圾回收器将新生代的内存空间分为两个区域:
- 对象区:存储对象
- 空闲区:空闲内存
垃圾回收时,将对象区复制到空闲区,之后交换角色,空闲区变成对象区,对象区变成空闲区。
这样,通过上述过程,无需递归操作就能高效回收垃圾数据。
垃圾回收之所以在新生代设置较小的内存空间,是因为这部分对象占用内存较少,回收频繁且时间成本较低。
老生代垃圾回收器
老生代的对象存在较长时间,使用标记 - 清除算法进行垃圾回收。
具体步骤:
标记阶段:从根元素开始遍历对象图,标记活动对象为灰色,非活动对象为黑色。标记过程通常采用增量标记,避免卡顿。
清除阶段:收集所有黑色对象,将其所占用内存释放。
标记 - 清除算法存在一个问题:会产生大量空闲内存,导致内存碎片。解决方案是采用标记 - 整理算法,集中回收内存碎片。
全停顿问题
使用标记 - 清除算法,必须暂停 JavaScript 执行进行垃圾回收,这种现象称为 全停顿。
在老生代垃圾回收时,垃圾回收运算符会使得主线程暂停,导致应用程序卡顿。
事实上,V8引擎的全停顿时间可能高达 200 毫秒,这对用户体验至关重要。为了降低老生代的卡顿问题,V8 推升了增量标记技术。
代际假说与回收策略
解决全停顿问题的关键在于对内存进行分类管理。Ek新生代
和 老生代
的划分基于对象生存周期的长短。新生代对象存活时间短,老生代存活时间长。
这种划分使得垃圾回收可以根据对象特性采用不同的算法,从而提高回收效率。这样才能在不影响用户体验的前提下,实现高效垃圾回收。
发表评论
最新留言
关于作者
