你们可曾了解,Javascript也具有自动垃圾收集机制^_^

1,009 阅读8分钟

阐述

仔细阅读本篇文章,可能会对您处理内存泄露有很大助益哟!^o^

JavaScript的自动垃圾收集机制就像是我们家里的自动垃圾桶。想象一下,你在家里吃东西,会产生一些垃圾,比如包装袋或果皮。你不需要每次都手动去扔这些垃圾,因为家里有一个自动垃圾桶,它会帮你收集这些不再需要的东西。

同样地,在JavaScript程序中,当我们创建变量或对象时,它们就像是我们产生的垃圾。当我们不再需要这些变量或对象时,它们就变成了垃圾,需要被清理掉。但是,我们不需要手动去清理它们,因为JavaScript有一个自动垃圾收集机制,就像那个自动垃圾桶一样。

这个自动垃圾收集机制会定期检查我们的程序,找出那些我们不再需要的变量或对象,然后把它们清理掉,释放出内存空间。这样,我们的程序就可以更加高效地运行,不会因为内存不足而变慢或崩溃。

具体来说,自动垃圾收集机制有两种常用的方法来确定哪些东西是垃圾。一种是标记清除,它就像是我们给还在用的东西打上标签,然后清理掉那些没有标签的东西。另一种是引用计数,它就像是我们数每个东西被用了多少次,如果没被用到(次数为0),那就被当作垃圾清理掉。

接下来我们来了解下什么是标清清除策略、引用计数

标记清除(Mark-and-Sweep)

  1. 标记阶段
  • 从根对象(在JavaScript中通常是全局对象)开始,垃圾回收器遍历所有的对象。
  • 在遍历过程中,对所有可达对象(即当前正在被使用或可以从根对象直接或间接访问到的对象)进行标记。这通常 意味着给这些对象设置一个特殊的标志,表示它们仍在被引用。
  • 同时,会移除在作用域内变量以及被这些变量引用的变量的标记,因为它们显然是可达的。
  1. 清除阶段
  • 在标记阶段完成后,垃圾回收器会检查所有对象,并清除(或回收)那些没有被标记的对象。这些对象因为没有被任何变量引用,所以被认为是不可达的,即不再被程序所使用。
  • 清除操作会释放这些对象占用的内存空间,使其可以被其他对象重新使用。

标记清除算法的优点在于它能够准确地识别出不再使用的对象,并有效地回收它们的内存。然而,这种算法也可能导致一些性能开销,特别是在大型应用或需要频繁进行垃圾回收的场景中。因此,现代的JavaScript引擎会对标记清除算法进行优化,以提高其效率和性能。

需要注意的是,虽然标记清除是JavaScript中主要的垃圾回收策略,但实际的垃圾回收过程可能还涉及其他技术,如引用计数(虽然存在一些问题,如循环引用)和更复杂的优化策略

那么问题来了,在我们的实际编写代码中,又是如何触发垃圾收集机制的呢?

避免内存泄漏

下面是一个简单的例子,它创建了一些对象,然后不再引用它们,从而触发垃圾收集:

// 创建一个对象
let myObject = {
  name: 'Example',
  value: 100
};

// 此时,myObject 引用了一个对象,这个对象是不能被垃圾收集的
console.log(myObject); // 输出对象的信息

// 断开所有对对象的引用
myObject = null;

// 在这里,由于没有任何变量引用之前创建的对象,
// JavaScript的垃圾收集器在适当的时机将会识别到这个对象不再可达,
// 并自动释放其占用的内存。

// 注意:我们不能直接观察垃圾收集的过程,
// 因为它是由JavaScript引擎在后台自动处理的。

在实际应用中,你通常不需要关心垃圾收集的具体过程,因为JavaScript引擎会为你自动管理内存。然而,理解垃圾收集的基本原理可以帮助你写出更高效、更不容易引发内存泄漏的代码。

要避免内存泄漏,你需要注意以下几点:

  1. 避免全局变量的滥用:全局变量在整个应用程序的生命周期内都存在,如果不小心使用,可能会导致内存占用持续增长。
  2. 及时解除引用:当你不再需要一个对象时,确保所有对它的引用都被解除(设置为null)。
  3. 注意闭包和回调:闭包可以保留其外部作用域的引用,如果闭包不再需要,但它仍然引用着外部对象,那么这些对象将不会被垃圾收集。
  4. 避免循环引用:在JavaScript中,即使两个对象相互引用,如果它们都没有被外部引用,那么它们仍然可以被垃圾收集。但是,如果有DOM元素和JavaScript对象之间的循环引用,可能会导致内存泄漏,因为DOM元素是由浏览器管理的,其生命周期可能与JavaScript对象不同。

以上四点注意事项,将会在下一篇文章中详细总结!敬请关注!

总之,虽然我们不能直接控制JavaScript的垃圾收集过程,但通过编写良好的代码实践,我们可以确保内存得到有效利用,并避免不必要的内存泄漏。

引用计数

什么是引用计数

官方一点的回答:

是计算机编程语言中的一种内存管理技术。它是指将资源(如对象、内存或磁盘空间等)的被引用次数保存起来,当这个被引用次数变为零时,系统就会自动释放这个资源的过程

形象一点的回答:

引用计数是一种跟踪对象被使用次数的技术,用于管理计算机内存中的对象。我们可以把“引用”想象成指向对象的“箭头”或者“链接”,而“计数”就是这些“箭头”或“链接”的数量。

每当我们创建一个对象,并有一个变量指向它时,就相当于有一个“箭头”指向这个对象,它的引用计数就是1。如果再有另一个变量也指向这个对象,那么又增加了一个“箭头”,引用计数就变成了2。

每当我们不再需要一个对象,并把指向它的变量设置为其他值或者删除这个变量时,就相当于移除了一个或多个“箭头”。每次移除“箭头”,引用计数就会减1。

当对象的引用计数减到0时,就意味着没有任何变量再指向这个对象了,它就像一个“孤岛”,没有任何“箭头”指向它。这时,系统就会知道这个对象不再被需要,于是就会释放它占用的内存空间。

简单来说,引用计数就是用来判断一个对象是否还在被使用的方法。如果一个对象没有被任何变量引用,那么它的引用计数就是0,我们就可以安全地释放它占用的内存。这种方法有助于我们更有效地管理计算机的内存资源,避免内存泄漏等问题。

我们看一段代码:

function ObjectWithRefCount(name) {  
this.name = name;  
// 初始化引用计数为1,因为对象创建时至少有一个引用(即自己)  
this.referenceCount = 1;  
  
this.addReference = function() {  
this.referenceCount++;  
console.log(`${this.name} 的引用计数增加: ${this.referenceCount}`);  
};  
  
this.removeReference = function() {  
this.referenceCount--;  
console.log(`${this.name} 的引用计数减少: ${this.referenceCount}`);  
  
// 如果引用计数为0,则释放对象  
if (this.referenceCount === 0) {  
console.log(`${this.name} 已经被释放。`);  
// 在实际场景中,这里可能会执行一些清理操作,比如解除事件监听、清除缓存等  
// 然后,我们可以将对象的引用设为null,帮助垃圾回收器回收内存  
this.name = null;  
}  
};  
}  
  
// 创建一个对象  
let obj = new ObjectWithRefCount("MyObject");  
  
// 增加对象的引用计数  
obj.addReference(); // 引用计数为2  
  
// 假设我们不再需要这个对象的一个引用,减少引用计数  
obj.removeReference(); // 引用计数为1  
  
// 当最后一个引用也被移除时  
obj = null; // 假设这是移除最后一个引用的操作,但在这个例子中我们还需要手动调用removeReference  
// obj.removeReference(); // 如果执行这行代码,引用计数将为0,并打印释放消息  

在这个例子中,创建了一个简单的Object构造函数,它有一个referenceCount属性来跟踪对象的引用次数。我们还将提供一个addReference方法来增加引用计数,以及一个removeReference方法来减少引用计数。当引用计数为0时,我们可以认为对象不再被引用,因此可以将其从内存中“释放”

但是,这个例子并不完全反映JavaScript的真实内存管理情况,因为在实际的JavaScript环境中,你不需要(也不能)手动管理引用计数。JavaScript引擎使用了更复杂的垃圾回收算法(如标记-清除或引用计数与标记-清除的结合)来自动管理内存。

当在JavaScript中设置一个变量为null或者超出其作用域时,如果没有其他变量引用该对象,垃圾回收器就会知道这个对象不再需要,并在适当的时机回收其占用的内存。因此,上面的例子主要是为了教育目的,展示引用计数的概念,而不是JavaScript的实际内存管理实践。

有疑问,欢迎评论区留言,大家一起探讨!