小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文同时参与 「掘力星计划」,赢取创作大礼包,挑战创作激励金
前言
💥 排雷,本文主要针对初学GC的读者
💥 排雷,如果觉得前面的文字太多,可以直接跳到【示例】
什么是垃圾回收?
根据 Wiki 的定义,垃圾回收(英语:Garbage Collection,缩写为GC)是指一种自动的内存管理机制。当某个程序占用的一部分内存空间不再被这个程序访问时,这个程序会借助垃圾回收算法向操作系统归还这部分内存空间。
当某个程序占用的一部分内存空间不再被这个程序访问时,这个程序会借助垃圾回收算法向操作系统归还这部分内存空间。垃圾回收器可以减轻程序员的负担,也减少程序中的错误。垃圾回收最早起源于LISP语言。[1][2]目前许多语言如Smalltalk、Java、C#、Go和D语言都支持垃圾回收器。
在C与C++等语言中,开发人员可以直接控制内存的申请和回收。但是在Java、C#、JavaScript语言中,变量的内存空间的申请和释放都由程序自己处理,开发人员不需要关心。也就是说Javascript具有自动垃圾回收机制
垃圾回收的好处?
如果没有GC,程序员必须自己手动进行内存管理,必须清楚地确保必要的内存空间,释放不要的内存空间。程序员在手动进行内存管理时,申请内存尚不存在什么问题,但在释放不要的内存空间时,就必须一个不漏地释放。这非常地麻烦。
如果忘记释放内存空间,该内存空间就会发生内存泄露,即无法被使用,但它又会持续存在下去。如果将发生内存泄露的程序放着不管,总有一刻内存会被占满,甚至还可能导致系统崩溃。——引自《垃圾回收的算法与实现》
理解JS里面的垃圾回收
JavaScript 中主要的内存管理概念是 可达性,如果可以从任何一个已经定义的变量开始,直接或者通过其他对象的引用来访问到某个对象,则该对象是可达的。
在 JavaScript 引擎中有一个被称作垃圾回收器的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。
示例1
如下,定义了1个全局变量 "user"
,它引用了对象 {name:"John"}
// user 具有对这个对象的引用
let user = {
name: "John"
};
如果重写user
的值
user = null;
这个时候,对象 {name:"John"}
不再被引用,垃圾回收器会认为它是垃圾数据并进行回收,然后释放内存。
示例2
现在在定义user对象的同时,在将它的引用复制给admin
let user = {
name: "John"
};
let admin = user;
在之前的深浅拷贝里有说过,对象直接用等号复制,复制的是它的引用👉地址
这个时候如果同样对user
的值进行重写,对象 {name:"John"}
不会被回收,因为它还在被admin
引用
user = null;
如果是同时对user
和 admin
重写,才会被回收
user = null;
admin = null;
示例3
一个更复杂的例子,假如有这样一个”家庭“,marry
函数通过让两个对象相互引用使它们“结婚”了,并返回了一个包含这两个对象的新对象。
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
这个时候产生的内存结构,当前所有对象都是可达的
如果删除以下这两个引用,John 现在是不可达的,并且将被从内存中删除,同时 John 的所有数据也将变得不可达。
delete family.father;
delete family.mother.husband;
垃圾回收之后,当前结构就变成了
注:上述情况只删除其中一个是没有用的,因为这个时候所有对象还是可达的。
示例3.1
和上面的示例相同
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
但现在我们直接把family
重写,会是什么样的情况?
family = null;
内存的内部状态将变成
这个时候,虽然John 和 Ann 仍然连着,都有传入的引用,但是它没有了外部对其的引用,这个时候它变成了一座“孤岛”,同样会从内存中被删除。
这个时候我们就需要提到一个“根”的概念:
根(root)这个词的意思是“根基”“根底”。在GC的世界里,根是指向对象的指针的“起点”部分。可达的标准是一定从根上出发找得到的,JavaScript中的根可以理解为是全局变量对象。
几个对象相互引用,但外部没有对其任意对象的引用,这些对象也可能是不可达的,并被从内存中删除。
垃圾回收内部算法
javascript中最常用的垃圾回收方式是 “mark-and-sweep”即标记清除;
示例
假如当前有一个这样的对象结构
根据标记清除算法,第一步会先标记所有的根
接着标记这些根的引用
……如果还有引用,继续标记
全部标记完之后,没有被标记到的,就会被作为垃圾回收
总结
- 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
- 当对象是可达状态时,它一定是存在于内存中的。
- 被引用与可访问(从一个根)不同:一组相互连接的对象可能整体都不可达。
参考资料:
wiki Garbage collection (computer science)
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐