windbg"自由"对象类型

gal*_*arm 8 .net c# garbage-collection memory-management windbg

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

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

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

编辑:一个建议是我可能正在创建大对象堆中的对象并且它变得脆弱,但事实并非如此,因为我已经看到所有"自由"对象都位于第2代堆中.

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

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

Pau*_*ane 7

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

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

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

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

  • 我明白.有没有办法迫使.NET将内存返回给操作系统或操纵它来执行此操作?我在这里遇到了严重的麻烦,因为我达到了非常高的内存量(直到OutOfMemory异常),这是免费的,并且由于某些原因.NET不会返回此内存.顺便说一下,我相信GC.Collect与常规自动GC调用有点不同,强制将内存返回给操作系统.(至少那是我在测试中看到的) (2认同)

Jon*_*son 6

顺便说一句,看起来你有一个众所周知的问题(至少在套接字大师中)大多数套接字服务器进入.Net.保罗已经触及了它的含义.要详细说明,出现问题的是,在套接字上的读/写期间,缓冲区被固定 - 这意味着不允许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));
  }
}
Run Code Online (Sandbox Code Playgroud)

在此之后,您将需要NetworkStream使用缓冲池中的一个来传入和交换传入缓冲区.Buffer::BlockCopy将有助于表现(不要使用Array::Copy).这很复杂而多毛; 特别是如果你使它具有异步能力.

如果你没有分层流(例如SSLStream< - > DeflateStream< - > XmlWriter等),你应该在.Net 4.0中使用新的套接字异步模式 ; 它绕着IAsyncResults传递的效率更高.因为您没有对流进行分层,所以您可以完全控制使用的缓冲区 - 因此您不需要使用NetworkStream子类路由.