2

我真的很难掌握 .NET 中托管/非托管代码之间的过早垃圾收集,并且想知道这里是否有人有很好的方法来解释它。

我们遇到的错误与此处描述的错误相似:http: //www.codeproject.com/Tips/246372/Premature-NET-garbage-collection-or-Dude-wheres-my

基本上,我们在调用非托管代码的对象上调用方法;支撑它的托管对象得到 GC 并调用它的终结器;需要 GC.KeepAlive 来阻止这种情况的发生:

(代码取自链接文章):

Foo a = new Foo();
while (true)
{
    FooBar b = new FooBar();
    b.WorkWith(a);
    GC.KeepAlive(b);
}

现在,我知道需要 GC.KeepAlive,但我不明白 GC 是如何得出没有 KeepAlive 就可以丢弃 b 的结论。运行时是否不知道 b 正在执行一个方法,即使它是一个进入本机代码的方法(特别是 'b' 已被用作方法调用中的 this 引用)?

为什么 'b' 在进入 WorkWith(..) 时有资格进行收集,为什么垃圾收集器不假设在 WorkWith 方法退出时可以使 'b' 符合条件?

我错过了什么?这实际上如何发挥作用?

更新感谢大家的回答。我觉得我现在更好地理解了这一点。仍在尝试为我们的特定 API 制定一个很好的解决方案,但我想我会把它留给一个单独的问题 :)

4

2 回答 2

3

抖动会生成一个内部表,该表描述了局部变量的存储位置以及它们何时开始和停止存储对象引用。该表将说明b变量在 WorkWith 调用中不再相关。它生成了传递给实例方法的this参数,之后不再使用,包括方法调用本身。

现在由 WorkWith() 方法来跟踪this对象引用的使用情况。如果该方法实际上是在本机代码中实现的,那么很可能会出现问题,这样的代码没有被 jitted,所以没有描述何时仍然相关的表格

因此,可以在本机代码运行b对对象进行垃圾收集。它会在程序中的另一个线程触发 GC 时发生。

GC.KeepAlive() 调用修改该表,将其扩展到方法调用之外。代码中最终存在我们看不到的缺陷,可能是某种与本机代码互操作的 C++/CLI 代码。应该是负责引用的代码这通常是失败的,使用 GC.KeepAlive() 是解决此类错误的有效解决方法。

您也许可以联系该组件的所有者,并建议他使用gcManagedToUnmanaged调试器助手来清除此类错误。它通过在转换为非托管代码时故意强制垃圾收集来工作。

于 2013-09-11T11:38:44.890 回答
0

虽然虚拟方法分派使事情稍微复杂一些,但实例方法的行为很大程度上类似于this作为第一个参数传递的静态方法。因此,在 classFoo中,具有字段int Bar的方法

void SetBar(int newBar) { Bar = newBar; }

内部等效于:

static void SetBar(Foo This, int newBar) { This.Bar = newBar; }

如果Foo是一个名为的字段MyBitmap,它持有IntPtr非托管位图的句柄,并且具有如下方法:

// Should call GC.SuppressFinalize, but doesn't.
static Byte[] ExportBitmapDataAndDispose(Foo This)
{
  IntPtr myBits = This.MyBitMap;
  if (myBits == 0) throw new ObjectDisposedException(...);
  int destSize = ExternalBitmapHandler.GetSize(myBits);
  var result = new Byte[destSize];
  ExternalBitmapHandler.CopyBits(myBits, ref result[0], destSize);
  ExternalBitmapHandler.ReleaseBits(myBits);
  myBits = 0;
}

编译器会看到在从中读取This字段后从未使用过。MyBitMap代码仍然需要由 标识的外部资源myBits,但垃圾收集器对此一无所知。从它的角度来看,如果 引用的对象This没有其他实时引用,则代码不应该关心这些对象是在那个时候不再存在还是被保留更长时间。事实上,它的假设是正确的,因为运行代码确实不会关心这些对象何时停止存在。不幸的是,在终结器存在的情况下,对象并不仅仅不复存在。相反,如果垃圾收集器注意到 Finalization Queue 拥有对 的唯一实时引用This,它可能会运行This.Finalize(),这可能反过来通知ExternalBitmapHandlermyBits不再需要由 标识的位图。

请注意,问题实际上不在于 的垃圾收集This在于. This终结不是垃圾收集,而是当 GC 发现如果不是已注册的终结器有资格立即销毁的对象时触发的操作。另一种看待它的方式是说当不再需要ExternalBitmapHandler由 标识的位图时应该通知它This.MyBitMap,但垃圾收集器唯一可以告诉的是何时This不再需要。一旦代码读This.MyBitMapmyBits,它就不再需要This并且非常高兴如果This不复存在,只要它没有破坏 `myBits. 代码的替代版本可能是:

static Byte[] ExportBitmapDataAndDispose(Foo This)
{
  IntPtr myBits = System.Threading.Interlocked.Exchange(ref This.MyBitMap, 0);
  GC.SuppressFinalize(This);
  if (myBits == 0) throw new ObjectDisposedException(...);

  int destSize = ExternalBitmapHandler.GetSize(myBits);
  try
  {
    var result = new Byte[destSize]; // Could throw OutOfMemoryException
    ExternalBitmapHandler.CopyBits(myBits, ref result[0], destSize);
  }
  finally
  {
    ExternalBitmapHandler.ReleaseBits(myBits);
  }
}

请注意,在这种情况下,没有KeepAlive,也没有将SuppressFinalize代码放在最后,它会推迟任何完成的可能性,直到一切都完成。一旦Interlocked.Exchange发生了,系统破坏就没有什么问题了This,因为它不再需要任何东西;系统需要让它保持足够长的时间才能执行SuppressFinalize,但之后它可能会消失并且没有人会注意到。

于 2013-09-13T17:44:17.673 回答