【问题标题】:Does a recursive setTimeout call cause a closure chain memory leak?递归 setTimeout 调用会导致闭包链内存泄漏吗?
【发布时间】:2019-11-19 04:40:06
【问题描述】:

我有以下场景:

let func = () => {
  //...
  let id = setTimeout(() => {
    console.trace();
    clearTimeout(id);
    func();
  }, 2000);
}

func();

例如,如果我们在 Chrome 控制台中运行以下代码,我们会得到以下堆栈跟踪

console.trace
(anonymous) @ VM89644:4
setTimeout (async)
func @ VM89644:3
(anonymous) @ VM89644:6
setTimeout (async)
func @ VM89644:3
(anonymous) @ VM89644:6
setTimeout (async)
func @ VM89644:3
(anonymous) @ VM89644:6
setTimeout (async)
func @ VM89644:3
(anonymous) @ VM89644:6
setTimeout (async)
func @ VM89644:3
(anonymous) @ VM89644:6
setTimeout (async)
func @ VM89644:3
(anonymous) @ VM89644:6
...
...
...

在每次迭代中不断增加。最终它达到了一个最大值(我认为在 35 次迭代之后),链 似乎 停止增长,但我很好奇它是否只是 Chrome 没有显示它。到底发生了什么?

【问题讨论】:

  • 那么这个问题呢? stackoverflow.com/questions/56951520/…
  • 我在上一个问题中没有正确传达我的担忧,当我尝试编辑它时它已经关闭。这就是为什么新问题的标题不同,侧重于闭包部分而不是堆栈框架部分,旨在以不同的方式为同一枚硬币的两面提供知识。

标签: javascript recursion memory-leaks settimeout


【解决方案1】:

递归 setTimeout 调用会导致闭包链内存泄漏吗?

没有。每次触发定时器后,浏览器中的定时器子系统都会释放它与定时器函数的链接,而定时器函数又会释放它与创建它的环境的链接,从而使它们都可以被回收。

在每次迭代中不断增加。最终它达到了一个最大值(我认为在 35 次迭代之后),链似乎停止增长,但我很好奇它是否只是 Chrome 没有显示它。到底发生了什么?

这是标准的内存管理,根据 JavaScript 引擎的内存管理启发式进行清理,并不总是主动清理。我怀疑如果你反复点击 devtools 的“收集垃圾”按钮(在内存选项卡上),你会注意到它回到了接近基线。

可能会阻止返回接近基线的一件事是 V8 的漂亮async stack traces。它们被宣传为“零成本”,但当然,这不可能完全正确,因为它们必须跟踪异步堆栈信息(即使只是作为字符串)。

【讨论】:

  • 我还是一头雾水。我尝试了收集垃圾的技巧,但它似乎并没有让它回到基线。此外,如果这是一种链表类型的结构,我看不到它的 part 是如何被 GC 处理的,因为引用仍将指向尾部,除非列表被显式破坏.
  • @Konstantine - 它不是链表结构。 1. 你运行func()。 2. 创建调用#1 到func() 的环境。 3. 创建回调 A(参考 env #1)。 4. 定时器调用回调A。 5. 回调调用func()。 6. 创建调用#2 到func() 的环境。 7. 创建回调 B(参考 env #2)。 7. 定时器释放指向回调 A 的链接。 8. 回调 A 和 env #1 都符合 GC 条件,它们只相互引用,回调 B 和 env #2 都没有引用它们中的任何一个。但是一个想法是异步堆栈跟踪可能会对内存产生较小的影响,添加到上述内容中。
猜你喜欢
  • 2013-07-25
  • 1970-01-01
  • 2021-10-06
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-23
  • 2021-09-25
相关资源
最近更新 更多