windbg“自由”对象类型

时间:2010-08-24 14:58:40

标签: c# .net memory-management garbage-collection windbg

在程序运行时监视程序的虚拟字节使用情况表明,通过执行某种操作,虚拟字节使用量在大约5分钟内上升了大约1GB。 该程序处理tcp套接字和它们之间的高数据传输吞吐量(~800Mbps)。

在windbg中加载程序的转储文件表明,内存使用率非常高且快速的原因是大约1GB的“免费”对象。 实际上,当我从程序的控制台屏幕调用垃圾收集器(gen 0,1& 2)时(在达到此状态之后),它释放了大约1GB的内存使用量。

我正在试图了解这些免费对象究竟是什么,为什么它们不会被垃圾收集器自动收集。

编辑:一个建议是我可能在大对象堆中创建对象并且它变得很脆弱但是事实并非如此,因为我已经看到所有“免费”对象坐在Gen 2 Heap。

其他建议是,由于固定对象,Gen 2 Heap可能会碎片化,但如果是这种情况,GC.Collect不会解决问题但实际上确实如此,我相信情况并非如此。

我与保罗讨论时怀疑的是,内存确实被释放,但是由于某些原因很少或仅在我手动调用GC.Collect时返回到操作系统。

2 个答案:

答案 0 :(得分:7)

它们不是免费的“物品”,它们是自由空间。 .NET不会立即释放它已用于操作系统的内存。任何空闲块都可以用于后续对象分配,只要它们适合空闲块(否则必须通过要求操作系统分配更多内存来扩展堆)。

垃圾收集器通过压缩第2代来努力将自由空间组合成大的可用块。这并不总是可行的:例如,应用程序可能会固定对象,这些对象可能会阻止垃圾收集器通过移动活动对象来组合自由空间到堆的前面。如果发生这种情况,应用程序的内存将被分解为无用的小块,这种影响称为“堆碎片”。

此外,还有一个大对象堆(LOH),其中分配了更大的对象。理由是存在与堆压缩相关的成本,因为必须复制数据,因此LOH不会被压缩,从而避免了这些成本。然而,另一方面是LOH可以变得容易碎片化,在活物体之间散布着小而不太有用的自由记忆块。

我建议运行dumpheap -stat。此命令将在列表的末尾报告任何碎片块。然后,您可以转储这些块以了解正在发生的事情。

答案 1 :(得分:5)

顺便说一句,看起来你有一个众所周知的问题(至少在套接字大师中)大多数套接字服务器进入.Net。 Paul已经触及了它的含义。要详细说明,出现问题的是,在套接字上的读/写期间,缓冲区被固定 - 这意味着不允许GC移动它(就像堆碎片一样)。当他们的实际内存使用量仅为500MB(由于如此严重的碎片)时,Coversant(开创了解决方案的人)看到了OutOfMemoryException。修复它完全是另一个故事。

你想要做的是在应用程序启动时分配大量的缓冲区(我目前正在做50MB)。在编写本文时,您会发现新的ArraySegment<T>(v2.0)和ConcurrentQueue<T>(v4.0)类特别有用。如果您还没有使用v4.0,那么管上会有一些无锁的队列。

// Pseudo-code.
ArraySegment<byte> CheckOut()
{
  ArraySegment<byte> result;
  while(!_queue.TryDequeue(out result))
    GrowBufferQueue(); //Enqueue a bunch more buffers.
  return result;
}

void CheckOut(ArraySegment<byte> buffer)
{
  _queue.Enqueue(buffer);
}

void GrowBufferQueue()
{
  // Verify this, I did throw it together in 30s.
  // Allocates nearly 2MB. You might want to tweak that.
  for(var j = 0; j < 5; j++)
  {
    var buffer = new byte[409600]; // 4096 = page size on Windows.
    for(var i = 0; i < 409600; i += 4096)
      _queue.Enqueue(new ArraySegment<byte>(buffer, i, 4096));
  }
}

在此之后,您将需要子类NetworkStream并将缓冲池中的一个缓冲池换出传入缓冲区。 Buffer::BlockCopy将有助于提高效果(> Array::Copy)。这很复杂而多毛;特别是如果你让它具有异步能力。

如果您没有对流进行分层(例如SSLStream&lt; - &gt; DeflateStream&lt; - &gt; XmlWriter等),则应使用new socket async pattern in .Net 4.0 ;在传递的IAsyncResult周围有更高的效率。因为您没有对流进行分层,所以您可以完全控制使用的缓冲区 - 因此您无需使用NetworkStream子类路由。