【问题标题】:Chrome/V8 does not garbage collect a circular reference?Chrome/V8 不会垃圾收集循环引用?
【发布时间】:2016-01-05 19:10:03
【问题描述】:

查看 Chrome 堆快照的这一部分:

它显示了堆中一个对象的保持器,据我所知和可以看到,它应该是垃圾,但尽管如此,它并没有被收集。

到根的“最短”路径毕竟是一条循环路径(它实际上从未到达根)。这让人不禁想知道,快照查看器是如何为它分配 12 的距离的呢?这仅仅是放弃之前通过循环所采取的步骤数吗?请注意距离永远不会低于 11。

我了解到,清理带有循环引用的子图可能需要几次迭代。但重复的强制收集(使用“时间轴”选项卡中的垃圾桶按钮)未能清理这些对象。

请注意,通过“185”引用探索最终会导致相同的system / Context @862399,因此实际上没有从根到该对象的路径(至少在此处不可见)。

是我疯了,还是垃圾收集器真的坏了?我不记得过去有这个问题。我在 Chrome 45.0.2454.101 上。 Beta 46.0.2490.64 的行为相同。

【问题讨论】:

  • 这些方法甚至可以被垃圾收集吗?你在创建闭包吗?您会无意中造成内存泄漏吗?
  • Google JavaScript style guide 有一个闭包示例,该闭包创建了导致内存泄漏的循环引用。如果这是 OP 的情况,那么这并不是什么新鲜事:123。不知道为什么,但现代 JS 引擎似乎没有解决这个非常常见的内存泄漏源的解决方案。
  • @GOTO0 我知道闭包会保留对它使用的封闭变量的引用(否则它怎么能起作用?)但它还保留未使用的封闭变量对我来说是新的,实际上很漂亮令人失望。但是,该样式指南说“循环引用,因此存在内存泄漏”。这不是仅适用于引用计数 GC 的过时声明吗?我认为标记和扫描应该使这成为一个非问题?如果那部分是错误的,那么该指南的其余部分是否值得信赖?
  • @GOTO0 我做了一个小测试:pastebin.com/fPxNxqSy。尽管x 返回的闭包被window 保留,但在那里创建的MyClass 实例m 并没有保留(如堆快照所示)。保留y 中的那个,因为eval 无法优化掉它。 (这是 V8 中的一个实现细节,虽然我怀疑其他现代浏览器的行为相同,但我还没有测试过。)
  • Google 风格的代码链接只是某人的建议,尽管是来自 google 的人,所以对于任何建议文章,您都应该这样对待。有一些众所周知的旧文章是基于证据的,有很棒的建议和例子。但是,您将找到的最佳文档将是您的 chrome 配置文件和内存使用情况。使用您的应用程序一段时间,然后将其保持打开状态,这将比一篇文章或我的/任何答案更能帮助您确定您的内存管理机会。

标签: javascript google-chrome garbage-collection v8 circular-reference


【解决方案1】:

老实说,我们需要快速查看您复制此代码的一些测试代码,但我对您所遇到的情况有一个大致的了解。如果我错了,您可以提供一些测试代码来证明这一点,请告诉我。

您似乎已经知道,为了“清理”,Javascript 不想再引用要释放的项目。

一个简单的例子:

// Currently not eligible for garbage
var myObj = {
    data : 'test'
};

// Reference data inside myObj
// A unique identifier to myObj.data now exists 
var myData = myObj.data;

// Whoops
myObj = 'something else';

// Because myData exists, so does data and can not be freed either, understandable
// This sounds simple and logical but most do not understand what is happening in the engine
// A relationship has been born between 'myData' and 'data' and as long as it exists, it is not eligible for garbage and remains in memory
console.log( myData );

您的代码可能比这更复杂,但这可能有助于解释如何在某处无法收集垃圾,因为范围链可以跟随引用。

考虑以下

function oops(text){

    function doStuff(){
        return text.toLowerCase();
    }
    return doStuff();
}

// 'doStuff' stays alive thanks to 'test' below.
var test = oops('closure');

函数doStuff 不会被垃圾回收,因为它被test 引用。

// You can see where this is headed. We have 2 references to the same 'doStuff' function object with separate unique identifiers now below.
var test2 = oops('closures...');

// This is now another unique identifier to 'doStuff'
var test3 = test2;

// doStuff survives yet still
test = 0;
test2 = 0;

// Now we let the function object of 'doStuff' be freed because there is no longer any references to it
test3 = 0;

这本质上是我们创建的内存泄漏。每次调用 oops 时,都会创建一个具有唯一标识符的函数对象 doStuff

避免这种情况的方法可能是

function doStuff( text ){
    return text.toLowerCase();
}

function oops( text ){
    return doStuff();
}

var test = oops( 'closure' );

现在我们没有内存泄漏。 doStuff 正在被调用,而不是创建

仔细查看您的代码,您会发现您可能正在某处执行此操作。

如果你在搞乱元素,我想你可能会,IBM has a good article about circular references 你可能想看看。


已经很晚了,其中一些未经测试,但理论仍然存在,所以如果我拼错了什么等,请告诉我,我明天可以看看这个页面的未来访问者。

【讨论】:

  • 感谢您的回答,但如上所述,我已经意识到闭包的保留能力,尽管显然不完全。尽管如此,如果我的堆快照中的值可以从 GC 根访问,为什么没有显示完整路径,距离减小到 1?这是否意味着快照查看器实际上是不可信的?正如问题中所说,我过去没有遇到过这个问题(尽管很难说是应用程序或 Chrome 的更改导致了这个问题)。
  • 我尝试在示例代码中重现问题,但没有成功。应该说,它发生的真实应用程序是相当大的,是一个相当大的单页游戏。它的内存使用量(泄漏前)可以达到几百兆。我不知道浏览器是否已经为此做好准备?
  • 当然,如果内存使用合理,浏览器可以处理几百兆,但内存越多延迟越高。如果我可以推荐一些东西。拍摄快照,使用您的应用程序,然后拍摄另一个并进行比较。使用 dom 元素,您可能会像本文中的人一样找到需要优化的地方:: blog.newrelic.com/2012/07/17/…
  • 我认为你的内部函数示例是错误的,它不会泄漏
  • 也许 Jesse 不小心写了“return doStuff()”。我认为他的意思是“返回 doStuff”(不调用 doStuff)
猜你喜欢
  • 1970-01-01
  • 2016-05-31
  • 2013-05-11
  • 1970-01-01
  • 2011-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多