• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

4.2 Garbage collection(垃圾回收)

JS 教程 tiantian 47次浏览 0个评论 扫描二维码

JavaScript 的内存管理是自进行的,是对开发者不可见的。我们创建原始类型变量,对象,函数等等,所有的这些都要占用内存。

那么在某些这类变量不再被需要的时候,会发生什么呢?JavaScript 引擎是如何发现这类变量,并且清除它们的呢?

可达性

JavaScript 中内存管理的核心概念是可达性。

简单理解,“可访问”的那些变量是指那些在代码的某处被使用或者被访问的变量。确保这些变量一定被存储于内存。

  1. 存在一个基本的绝对可访问的系列变量,它们因为某些很明显的原因一定不可以被删除。

举个例子:

  • 当前函数的本地变量和参数。
  • 当前函数调用栈中所有栈帧使用的其他函数的变量和参数。
  • 全局变量
  • 其他的变量,比如内部变量等等。

这些值又被统称为 root(根)。

  1. 如果一个变量可以被 root 中的变量通过一次或者多次的引用,最终链在一起,那么该变量也是可达的。

举个例子,如果存在一个本地变量,该变量具有一个属性,引用到另一个对象,那个对象就是可达的。而且,该对象包含或者引用的所有变量也是可达的。更详细的示例在后面。

在 JavaScript 引擎的内部有一个被称为garbage collector(垃圾收集器)的后台进程。它会监控所有的对象,并且负责移除那些变得不再可达的对象和变量。

一个简单示例

此处,我们来看一个最简单的示例:

// user has a reference to the object
let user = {
  name: "John"
};

4.2 Garbage collection(垃圾回收)

上图中的箭头表示一个对象引用。那个全局变量"user"引用那个对象{name: "John"},我们可以简称那个对象为John。John 对象的"name"属性存储的是一个原始值,所以它直接被存储于对象中。

如果变量user被复写为null,那个引用就会丢失:

user = null;

4.2 Garbage collection(垃圾回收)

现在,John 就变得不可达了。代码中已经无法访问该对象了,因为没有任何引用指向它。垃圾收集器将会消除对象的数据,并释放其占用的内存。

两个引用

现在,假如我们已经将user引用复制给了admin

// user has a reference to the object
let user = {
  name: "John"
};

let admin = user;

4.2 Garbage collection(垃圾回收)

现在,我们如果将user复写为null

user = null;

此时,那个对象就还是可达的,可通过全局变量admin访问该对象,该对象依然存储于内存中。如果我们再次重写adminnull,那么该对象就会被移除,占用的内存被释放。

内链对象

现在,看一个复杂的示例。一个 family:

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});

函数marry的功能是通过给予两个参数为彼此的属性引用来结合两个对象,并且返回一个全新的对象,包含了上述的那两个对象。

这最终的内存结构演示图如下:

4.2 Garbage collection(垃圾回收)

可以发现,现在所有的对象都是可达的。

现在,我们来移除两个引用:

delete family.father;
delete family.mother.husband;

4.2 Garbage collection(垃圾回收)

如果只移除两个引用中的某一个是不足够的,因为这样会使得所有的对象仍然是可达的。

但是,如果我们移除两个引用的话,那么可以发现 John 就没有任何的输入型引用了。

4.2 Garbage collection(垃圾回收)

输出型的引用不影响对象的可达性。只有输出型引用才会影响对象的可达与否。所以,现在 John 变得不可达了,所以 john 会从内存中被移除,而且其包括的所有数据也会变得不可达。

在经过了垃圾回收之后:

4.2 Garbage collection(垃圾回收)

不可达孤岛

有时,某些内联的对象联合体可能会一起变得不可达,从而一起从内存中被删除。

对于如上示例相同的代码,加入执行:

family = null;

内存简图将演变成如下所示:

4.2 Garbage collection(垃圾回收)

上述的示例演示了可达性概念的重要性。

很明显,那个 John 和 Ann 仍然是链接的,两个对象都具有输入性引用。但是它们仍然是不可达的。

上述的那个"family"对象失去了来自 根(root) 的链接,而且也没有了其他来自 根(root) 的链接,所有整个“对象岛屿”变得不可达,从而被全部移除。

内部算法

基本的垃圾回收算法是“mark-and-sweep”(标记与交互)算法。

该算法一般具有下列的”垃圾回收“步骤:

  • 垃圾回收器收集 root,并对他们进行”marks”(标记,记忆)。
  • 然后,以 root 为出发点,访问并标记它们的输出型引用变量。
  • 然后,以 输出型引用变量 为出发点,进行深入访问并标记它们的输出型引用变量。所有的访问对象都进行了标记,这样就不会访问同一个对象两次。
  • 一直迭代,直到所有的可访问对象都被标记。
  • 将内存中所有没有被比较的对象清除出内存。

举个例子,假设我们的对象结构见图如下所示:

4.2 Garbage collection(垃圾回收)

我们可以容易地发现一个“对象孤岛”位于图片的右侧。现在,我们来看看“mark-and-sweep”垃圾回收算法是如何处理它的。

首先,第一步是标记 根(root):

4.2 Garbage collection(垃圾回收)

然后,标记 root 的引用变量:

4.2 Garbage collection(垃圾回收)

接着,标记 引用变量的引用变量,一直迭代下去:

4.2 Garbage collection(垃圾回收)

迭代结束后,那些不可访问的变量就被认为是不可达的变量,可以考虑删除它们:

4.2 Garbage collection(垃圾回收)

这就是垃圾回收的简单工作原理演示。

实际上,JavaScript 引擎在此基础上引入了很多优化工作,使得该过程运行更加快速,减少对脚本执行的影响。

其中的一些重要优化:

  • 分代收集 —— 对象被划分为两个集合:”new ones(新生代)” 和 “old ones(老年代)”。在实际的编程实践中,很多对象被创建,完成它们的工作和功能,然后很快地不再被使用,这样的对象可以被激进地清除。而有些对象被创建后会在内存中生存较长的时间,变成了“老生代”对象,这样的对象可以减少检查它们可达性的频率。
  • 递进收集 —— 如果存在非常多的对象,然后我们尝试一次性地遍历和标记整个的项目对象集合,这将花费大量的时间,并可能导致代码执行过程的可见性延迟。所有,引擎可以尝试将垃圾回收过程划分为几个步骤,那些步骤可以分离地一个接一个执行。这种方式需要某些额外的标记保持,用来追踪它们之间的对象变化,但是,这样操作就将一个较长的执行延迟替换为多个短小的执行延迟。
  • 空闲收集 —— 垃圾回收器只在 CPU 处于空闲状态时才尝试运行,这样可以减少垃圾回收对执行代码的影响。

当然,业界对于垃圾回收算法还存在许多其他的优化和调整策略。远远多于我在本章节简单描述的那些优化措施。本文不可能对它们进行全部地讲解,因为不同的引擎可能实现了不同的优化算法和技术。而且,更重要的是,随着引擎的迭代,优化算法和技术一直在改变。所以,在没有实际需求的情况下,太深入到学习优化算法的这个领域可能并不明智。当然,除非你将之看作一种个人爱好,那么,我们在本文的后面为你提供了一些资源链接。

总结

你需要知道的主要知识点如下:

  • 垃圾回收是自动进行的。开发者无法强制执行或者停止它。
  • 对象在它们可达的情况下将保留在内存中。
  • 被引用并不等同于可达(与 root 链在一起):一系列的内联对象可能在整体上变得不可达。

现代的 JavaScript 引擎对于垃圾回收都实现了一系列的高级优化算法和技术。

一本通用的书籍:”The Garbage Collection Handbook:The Art of Automatic Memory Management” (R. Jones et al) 覆盖了垃圾回收算法的基本内容。

如果你熟悉更底层的语言,那么关于 V8 引擎垃圾回收器的更多详细信息可以查看这篇文章: A tour of V8: Garbage Collection.

[V8 blog] 按照时间的推进,引擎的迭代提供了很多内存管理相关的文章。自然地,为了学习垃圾回收,你最好首先准备好一个 V8 的源代码,然后阅读 V8 的开发者Vyacheslav Egorov的博客和教程。当然,除了 V8,业界还存在许多其他的执行引擎,但是它们在很多方面都是类似的。

深入了解执行引擎的内部细节在需要对代码进行低层次的优化时是非常重要的。所以,我们建议你在熟练掌握了 JavaScript 语法和功能之后,可以考虑深入学习执行引擎的细节知识。


天天编码 , 版权所有丨本文标题:4.2 Garbage collection(垃圾回收)
转载请保留页面地址:http://www.tiantianbianma.com/garbage-collection.html/
喜欢 (0)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址