/ 9 分钟阅读
本文发布于 2533 天前,文中所述的信息可能已发生改变或更新。
虽然在 JavaScript 中不用自己管理内存,但是了解原理可以在关键时候快速搜索到解决办法,毕竟很多时候遇到问题是连搜索什么关键词都想不起来呀 😂
JavaScript 内存管理于我们来说是自动的、不可见的。我们创建的原始类型、对象、函数等等,都会占用内存。
当这些数据不被需要后会发生什么?JavaScript 引擎如何发现并清除他们?
JavaScript 内存管理的关键概念是可触及(Reachability)。
简单来说,“可触及”的值就是可访问的,可用的,他们被安全储存在内存。
以下是一些必定“可触及”的值,不管出于任何原因,都不能删除:
这些值都称为 root。
其他值是否可触及视乎它是否被 root 及其引用链引用。
假设有一个对象存在于局部变量,它的值引用了另一个对象,如果这个对象是可触及的,则它引用的对象也是可触及的,后面会有详细例子。
JavaScript 引擎有一个垃圾回收后台进程,监控着所有对象,当对象不可触及时会将其删除。
// user 引用了一个对象
let user = {
name: "John",
};

箭头代表的是对象引用。全局变量 "user" 引用了对象 {name: "John"}(简称此对象为 John)。John 的 "name" 属性储存的是一个原始值,所以无其他引用。
如果覆盖 user,对 John 的引用就丢失了:
user = null;

现在 John 变得不可触及,垃圾回收机制会将其删除并释放内存。
如果我们从 user 复制引用到 admin:
// user 引用了一个对象
let user = {
name: "John",
};
let admin = user;

如果重复一次这个操作:
user = null;
……这个对象是依然可以通过 admin 访问,所以它依然存在于内存。如果我们把 admin 也覆盖为 null,那它就会被删除了。
这个例子比较复杂:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman,
};
}
let family = marry(
{
name: "John",
},
{
name: "Ann",
},
);
marry 函数让两个参数对象互相引用,返回一个包含两者的新对象,结构如下:

暂时所有对象都是可触及的,但我们现在决定移除两个引用:
delete family.father;
delete family.mother.husband;

只删除一个引用不会有什么影响,但是两个引用同时删除,我们可以看到 John 已经不被任何对象引用了:

即使 John 还在引用别人,但是他不被别人引用,所以 John 现在已经不可触及,将会被移除。
垃圾回收后:

也可能有一大堆互相引用的对象整块(像个孤岛)都不可触及了。
对上面的对象进行操作:
family = null;
内存中的情况如下:

这个例子展示了“可触及”这个概念的重要性。
尽管 John 和 Ann 互相依赖,但这仍不足够。
"family" 对象整个已经切断了与 root 的连接,没有任何东西引用到这里,所以这个孤岛遥不可及,只能等待被清除。
基础的垃圾回收算法被称为“标记-清除算法”(“mark-and-sweep”):
举个例子,假设对象结构如下:

很明显右侧有一个“孤岛”,现在使用“标记-清除”的方法处理它。
首先,标记 root:

然后标记他们的引用:

……标记他们引用的引用:

现在没有被访问过的对象会被认为是不可触及,他们将会被删除:

这就是垃圾回收的工作原理。
JavaScript 引擎在不影响执行的情况下做了很多优化,使这个过程的垃圾回收效率更高:
除此以外还有很多对垃圾回收的优化,在此就不详细说了,各个引擎有自己的调整和技术,而且这个东西一直随着引擎的更新换代在改变,如果不是有实在的需求,不值得挖太深。不过如果你真的对此有浓厚的兴趣,下面会为你提供一些拓展链接。
重点:
《The Garbage Collection Handbook: The Art of Automatic Memory Management》(R. Jones 等)一书中提及了现代引擎实现的加强版垃圾回收算法。
如果你熟悉底层编程,可以阅读 A tour of V8: Garbage Collection 了解更多关于 V8 垃圾回收的细节。
V8 blog 也会经常发布一些关于内存管理的文章。学习垃圾回收算法最好还是先学习 V8 的实现,阅读 Vyacheslav Egorov(V8 工程师之一)的博客。这里提及 V8 是因为在互联网上关于 V8 的文章比较多。对于其他引擎,很多实现都是相似的,但是垃圾回收算法上区别不小。
对引擎的深入理解在做底层优化的时候很有帮助。在你熟悉一门语言之后,这是一个明智的研究方向。
/ 9 分钟阅读
本文发布于 2533 天前,文中所述的信息可能已发生改变或更新。
虽然在 JavaScript 中不用自己管理内存,但是了解原理可以在关键时候快速搜索到解决办法,毕竟很多时候遇到问题是连搜索什么关键词都想不起来呀 😂
JavaScript 内存管理于我们来说是自动的、不可见的。我们创建的原始类型、对象、函数等等,都会占用内存。
当这些数据不被需要后会发生什么?JavaScript 引擎如何发现并清除他们?
JavaScript 内存管理的关键概念是可触及(Reachability)。
简单来说,“可触及”的值就是可访问的,可用的,他们被安全储存在内存。
以下是一些必定“可触及”的值,不管出于任何原因,都不能删除:
这些值都称为 root。
其他值是否可触及视乎它是否被 root 及其引用链引用。
假设有一个对象存在于局部变量,它的值引用了另一个对象,如果这个对象是可触及的,则它引用的对象也是可触及的,后面会有详细例子。
JavaScript 引擎有一个垃圾回收后台进程,监控着所有对象,当对象不可触及时会将其删除。
// user 引用了一个对象
let user = {
name: "John",
};

箭头代表的是对象引用。全局变量 "user" 引用了对象 {name: "John"}(简称此对象为 John)。John 的 "name" 属性储存的是一个原始值,所以无其他引用。
如果覆盖 user,对 John 的引用就丢失了:
user = null;

现在 John 变得不可触及,垃圾回收机制会将其删除并释放内存。
如果我们从 user 复制引用到 admin:
// user 引用了一个对象
let user = {
name: "John",
};
let admin = user;

如果重复一次这个操作:
user = null;
……这个对象是依然可以通过 admin 访问,所以它依然存在于内存。如果我们把 admin 也覆盖为 null,那它就会被删除了。
这个例子比较复杂:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman,
};
}
let family = marry(
{
name: "John",
},
{
name: "Ann",
},
);
marry 函数让两个参数对象互相引用,返回一个包含两者的新对象,结构如下:

暂时所有对象都是可触及的,但我们现在决定移除两个引用:
delete family.father;
delete family.mother.husband;

只删除一个引用不会有什么影响,但是两个引用同时删除,我们可以看到 John 已经不被任何对象引用了:

即使 John 还在引用别人,但是他不被别人引用,所以 John 现在已经不可触及,将会被移除。
垃圾回收后:

也可能有一大堆互相引用的对象整块(像个孤岛)都不可触及了。
对上面的对象进行操作:
family = null;
内存中的情况如下:

这个例子展示了“可触及”这个概念的重要性。
尽管 John 和 Ann 互相依赖,但这仍不足够。
"family" 对象整个已经切断了与 root 的连接,没有任何东西引用到这里,所以这个孤岛遥不可及,只能等待被清除。
基础的垃圾回收算法被称为“标记-清除算法”(“mark-and-sweep”):
举个例子,假设对象结构如下:

很明显右侧有一个“孤岛”,现在使用“标记-清除”的方法处理它。
首先,标记 root:

然后标记他们的引用:

……标记他们引用的引用:

现在没有被访问过的对象会被认为是不可触及,他们将会被删除:

这就是垃圾回收的工作原理。
JavaScript 引擎在不影响执行的情况下做了很多优化,使这个过程的垃圾回收效率更高:
除此以外还有很多对垃圾回收的优化,在此就不详细说了,各个引擎有自己的调整和技术,而且这个东西一直随着引擎的更新换代在改变,如果不是有实在的需求,不值得挖太深。不过如果你真的对此有浓厚的兴趣,下面会为你提供一些拓展链接。
重点:
《The Garbage Collection Handbook: The Art of Automatic Memory Management》(R. Jones 等)一书中提及了现代引擎实现的加强版垃圾回收算法。
如果你熟悉底层编程,可以阅读 A tour of V8: Garbage Collection 了解更多关于 V8 垃圾回收的细节。
V8 blog 也会经常发布一些关于内存管理的文章。学习垃圾回收算法最好还是先学习 V8 的实现,阅读 Vyacheslav Egorov(V8 工程师之一)的博客。这里提及 V8 是因为在互联网上关于 V8 的文章比较多。对于其他引擎,很多实现都是相似的,但是垃圾回收算法上区别不小。
对引擎的深入理解在做底层优化的时候很有帮助。在你熟悉一门语言之后,这是一个明智的研究方向。