可以强制对象在第 1 代或第 2 代而不是第 0 代中被垃圾回收吗?

Possible to force an object to be garbage collected in Gen 1 or Gen 2 and not in Gen 0?

面试官问了我一个奇怪的问题,我找不到答案。

Is it possible to force an object to be garbage collected in Gen 1 or Gen 2 and not in Gen 0?

你不能...但是你可以将对象的寿命延长到某个时间点。方法内部最简单的方法是有一个:

var myObject = new MyObject();

... some code
... some other code

// The object won't be colleted until this point **at least**
GC.KeepAlive(myObject)

或者您可以使用 GCHandle:

var myObject = new MyObject();
this.Handle = GCHandle.Alloc(new object(), GCHandleType.Normal);

(其中 this.Handle 是您对象的一个​​字段)

或许还有另一种方法:

this.Handle.Free();

GCHandleType.Normal的描述是:

You can use this type to track an object and prevent its collection by the garbage collector. This enumeration member is useful when an unmanaged client holds the only reference, which is undetectable from the garbage collector, to a managed object.

请注意,如果您在 class 中执行此操作(GCHandle "this"),您应该实施 IDisposable 模式以释放 this.Handle .

好的...技术上你可以:

var myObject = new MyObject();
GC.Collect(0); // collects all gen0 objects... Uncollected gen0 objects become gen1
GC.KeepAlive(myObject);

现在 myObject 是 gen1...但这并不能真正解决您的问题:-)

根据GC.Collect的文档:

Forces an immediate garbage collection of all generations.

那么是的,你可以做到。只需强制 collection.

如此处所述,Fundamentals of Garbage Collection,在生存和晋升下:

Objects that are not reclaimed in a garbage collection are known as survivors, and are promoted to the next generation.

因此,如果 object 在您强制 collection 之前处于第 0 代,它现在将处于第 1 代。

好的,然后,如果我不再有对 object 的引用怎么办?如果我强制使用 collection 会发生什么?好吧,它很可能会被收集起来。可以修复吗?好吧,取决于你所说的 "fixed" 是什么意思,但是是的。

向 object 添加终结器。终结器将在 collection 期间将 object 移到单独的列表中,为终结器线程完成它做好准备。只有在终结器处理后,它才会再次符合 collection 的条件,但到那时它已经被提升了。

另请注意,如果您添加终结器是因为您需要处理没有引用的情况,您实际上不需要再强制 collection ,因为这是一个一个非常具体的面试问题,你现在已经保证它能在第 0 代存活下来。

所以两个步骤,其中任何一个都将保证 object 不在第 0 代中收集:

  1. 添加终结器
  2. 坚持对 object 的引用并强制 collection.

警告:垃圾 collection 已记录在案,但在 .NET 的生命周期中以各种方式和时间进行了调整。文档和上面的文本是否可以被视为固定不变的文档,或者这里是否进行了优化,例如可以使 GC.Collect not 强制 collection 因为collection 已经在进行中我不知道。

既然你是在面试的背景下问的,我会说以上应该是你对面试官的回答,但在实践中不要依赖任何这些。


GUIDELINE:依赖垃圾收集器的内部结构是生成脆弱代码的一种方法。

不要这样做

我唯一看到的真正与 GC.Collect 混为一谈的好地方,即使在那里也是可疑的,或多或少是在一个大批量交易的 single-threaded 系统中。例如,定期处理大批量数据的 Windows 服务可以执行一批,然后强制 collection 减少内存占用,直到下一批到期。

但是,这是一个高度专业化的案例。除了让面试官满意之外,GC.Collect 不应(作为准则)出现在您的生产代码中。

是的。

public class WillAlwaysBeCollectedInGen1or2
{
  ~WillAlwaysBeCollectedInGen1or2()
  {
  }
}

因为它有一个终结器,并且没有代码调用 GC.SuppressFinalize(this),所以它总是会在终结队列中结束,而不是在它第一次符合收集条件时被收集,这意味着它总是会在第一代以外的一代收集。

当然,因为我们通常希望发生相反的情况(尽快收集对象),这说明了为什么除非实际需要,否则不应定义终结器,并且应该始终调用 GC.SuppressFinalize(this) 如果对象处于最终化没有做任何有用的状态(最明显的是它被显式处理,但也可能有其他情况)。

那么,这些知识的用处就在于此;知道不该做什么。

通过扩展,您可以强制在 Gen1 或 Gen2 中收集任意对象,只需在此类对象中持有对它的强引用:

public class PreventGen0Collection
{
  private object _keptAlive;
  public PreventGen0Collection(object keptAlive)
  {
    _keptAlive = keptAlive;
  }
  ~PreventGen0Collection()
  {
  }
}

因为 PreventGen0Collection 本身至少在第 1 代之前无法收集,如上所述,传递给其构造函数的对象甚至无法在 GC 扫描中进行收集,因此将被提升传给下一代。

同样,这演示了要避免的代码; finalisable 不仅会提升一个对象,还会提升整个图。不要在不需要时使用终结器,并在可能时抑制终结器。