【问题标题】:Java/.NET mark a reference as "do not follow" for the garbage collector?Java/.NET 将垃圾收集器的引用标记为“不遵循”?
【发布时间】:2013-08-03 00:09:06
【问题描述】:

在 .NET/Java 中,您可以将引用包装在具有与垃圾收集器相关的神奇属性的类中(例如 WeakReference 等)

对于“不遵循”引用的想法似乎没有这样的类 - 即一种告诉垃圾收集器从指定引用开始有一个复杂的对象子图并且不应该收集它的方法. (所以不要浪费 CPU 周期来标记和扫描该子图)。

有人知道为什么没有这个功能吗?

非常感谢

【问题讨论】:

  • 对象仍然需要被遍历,因为它们可能引用其他对象。基本上,它们将成为根集的补充。
  • 通过将它们标记为“不关注”,您会说应该保留它们引用的任何内容。并且可能从 gc 第一次将引用观察为“不遵循”的点来看,它应该假设下面包含的子图是不可变的。
  • GC 需要以某种方式确保该图中的每个引用都是不可变的。否则,可能会收集违反语言和虚拟机安全的引用对象。
  • 是的 - 我猜它可以将它复制到一个单独的内存区域,这会在随后写入时引发访问冲突。

标签: java .net garbage-collection pass-by-reference


【解决方案1】:

该功能不存在,因为它不会按照您的想法进行。标记和清除收集器遍历对象图并记录应该保留的对象。如果您告诉它不要标记某些子图,那么收集器会在它执行任何清理操作时丢弃所有这些对象。唯一有用的引用状态是“保留这个”(在主类中锚定的图表中保留一个引用)、“扔掉这个”(不要保留任何可以从那里到达的引用)和“你可以选择是否扔掉这个离开”(WeakReference)。

【讨论】:

  • 谢谢,你说得对,“不遵循”对于实际实现来说并不是一个很好的描述——但我认为这不会改变整体想法。如果您将其重命名为“不可变且不可收集”,那么这可能更有意义吗?无论 gc 使用什么标记机制,都可以在第一次观察时使用,从那里永久标记所有内容,然后不理会它。或者将其复制并压缩到不是 gc'd 的不同内存区域?
  • 标记和清除垃圾收集器不会保留以前运行的任何标记信息;它从一组完全空的对象开始保留并从那里添加项目。即使这在标记和清除收集器的上下文中是有意义的,但是,JLS 不会指定它,因为没有关于如何实现垃圾收集器的要求(或者即使 JVM 有一个)。跨度>
  • 复制到单独内存的想法怎么样?是否有可能从“努力工作”的角度来解决这个问题?
  • 这在具有托管内存(JVM、.NET CLR)的系统中是不切实际的。这样一个系统的全部意义在于,当你完成它时,你不必显式地释放内存,虽然你当然可以选择“冻结”对象(Ruby 和其他一些语言支持它),尝试告诉 GC 某些对象应该放在一个单独的区域并且永远不会被释放是自找麻烦(需要 GC 特殊情况,DOS 或内存泄漏的风险......)。
  • "需要 GC 特殊外壳" - 是的,根据定义,这不是一件坏事。 “DOS 的风险” - 它可能是运行时的特权操作,“内存泄漏” - 根据定义,您再次以某种方式明确要求这样做 - 您是在说“我最了解,不要担心这个子图”。我认为尚未提出反对此想法的技术原因。
【解决方案2】:

问题在于 GC 的设计是假设一切都死了,然后找到 live 对象,然后收集其余的。为了使您的想法可行,您必须反转方法以使 GC 从所有活着的东西开始并找到 dead 对象。

考虑这种情况。您有一个对象 A,即您的特殊“不要浏览我的图表”对象。你的图表是 A->B->C。在找到活动对象的 GC 中,它无法知道 B 和 C 是活动的,因为“不要导航我的图形”标志会阻止它找到 B 和 C。所以 A 保持活动但 B 和 C收集。不好。

那么为什么 GC 会搜索活动对象而不是死对象?为了找到活动对象,GC 从它确定仍在使用中的引用开始(称为根)。这些通常是堆栈上的引用(当前运行方法中的局部变量)、静态引用和其他一些引用。这些根引用的每个对象,递归地追溯到图的末尾,肯定仍在使用中,无法收集。其他都是垃圾。

如果您实现了逆向方法并假设所有内容都处于活动状态并尝试搜索死对象,则您必须查看堆中的每个对象,找出引用它的对象,并追踪该图直到它用完或者你找到一个根。如果它找到了根,那么链中的所有东西都是有生命的。如果不是,则链中的所有内容都已死。

这两种方法的区别在于,从根源开始需要的工作量要少得多。您遵循的每个参考都保证有效。一个对象可能引用一千个其他对象,但您只需要从根开始穿过它一次即可证明它和这千个对象是活着的。相反,如果您从未从根目录访问过该对象,那么您无需浏览这数千个引用就知道它已经死了。

此外,对象存储它们引用的内容,而不是引用它们的内容。给定一个对象,找出是否有人引用它需要搜索所有其他对象的引用。或者您可以在每个对象上存储一个裁判列表,但现在每个引用的内存是原来的两倍。

在 .Net 中,有 GCHandle 类,它允许您创建一个被视为根的引用。因此不会收集对象的子图。但仍必须导航子图以了解这些对象是什么。

简而言之,GC 以这种方式设计是为了简化和提高性能。如果您不标记/清除对象的子图,则子图中的对象可能会被垃圾回收。这与您想要的相反。

【讨论】:

    【解决方案3】:

    了解 GC 行为的最简单方法是前往保龄球馆,观察球员掷出第一个球后会发生什么。机器识别所有仍然站立的瓶,将它们移出车道,盲目地清除车道,然后将站立的瓶放回车道上。机器不关心哪些针被击倒甚至多少(除非所有十个针都被击倒,在这种情况下它会立即前进到下一帧)。即使有办法标记被击倒的瓶,这种标记对置瓶机也无济于事。

    要考虑的另一件事是,GC 框架最基本的优点之一是没有悬空引用之类的东西。只要在任何地方都存在可达引用,对象就会存在,并且在最后一个可达引用被销毁时将不再存在。为了允许弱引用之类的事情,系统首先识别可以通过强引用到达的所有对象。完成后,系统将遍历弱引用列表,并使目标未标记为活动的任何弱引用无效。它还将遍历覆盖Finalize 的对象列表,并将该列表中但未在其他任何地方引用的任何项目添加到需要立即清理的对象列表中。覆盖 Finalize 或存在弱引用的对象将继续存在,直到 GC 清除所有可以通过任何方式到达它的引用;然而,无论 GC 何时运行以回收空间,大多数对象都会在最后一个引用被覆盖后立即停止存在。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-10-04
      • 1970-01-01
      • 2018-06-10
      • 1970-01-01
      • 2016-05-31
      • 2011-11-07
      • 2016-11-18
      • 1970-01-01
      相关资源
      最近更新 更多