Windbg“免费”对象类型

发布于 2024-09-15 15:11:00 字数 522 浏览 8 评论 0原文

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

在 Windbg 中加载程序的转储文件表明,内存使用率非常高且快速的原因是大约 1GB 的“空闲”对象。 事实上,当我从程序的控制台屏幕调用垃圾收集器(第 0、1 和 2 代)时(进入此状态后),它释放了大约 1GB 的内存使用量。

我试图了解这些免费对象到底是什么以及为什么它们不被垃圾收集器自动收集。

编辑:一个建议是,我可能在大型对象堆中创建对象,并且它变得碎片化,但事实并非如此,因为我看到所有“自由”对象都位于 Gen 中2 堆。

其他建议是,也许第 2 代堆会因为固定对象而变得碎片,但如果是这种情况,GC.Collect 不会解决问题,但它实际上会解决问题,所以我相信情况也并非如此。

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

Monitoring my program's Virtual Bytes usage while it is running showed that by doing some kind operations, the virtual bytes usage goes up by about 1GB in about 5 minutes.
The program deals with tcp sockets and high data transfer throughput between them (~800Mbps).

Loading a dump file of the program in windbg showed that the reason for the very high and fast memory usage is about 1GB of "free" objects.
Indeed, when I call the garbage collector (gen 0, 1 & 2) from the console screen of the program (after getting it to this state) it frees up about 1GB of memory usage.

I'm trying to understand what exactly are these free objects and why aren't they garbage collected by the garbage collector automatically.

Edit: One suggestion was that I may be creating the objects in the Large Object Heap and it becomes fragmanted but this is not the case as I've seen that all of the "free" objects sits in Gen 2 Heap.

Other suggestion was that maybe Gen 2 Heap gets fragmented because of pinned objects but if this was the case, GC.Collect wouldn't fix the problem but it actually does so I believe this is not the case as well.

What I suspect from the discussion with Paul is that the memory does gets freed but is from some reason returned to the OS rarely or only when I manually call GC.Collect.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

愛上了 2024-09-22 15:11:00

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

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

此外,还有一个大对象堆(LOH),其中分配较大的对象。基本原理是,堆压缩会产生相关成本,因为数据必须四处复制,因此 LOH 不会被压缩,从而避免了这些成本。然而,不利的一面是,LOH 很容易变得碎片化,小的、不太有用的空闲内存块散布在活动对象之间。

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

They are not free 'objects', they are free space. .NET does not release memory it has used back to the operating system immediately. Any free blocks can be used for subsequent object allocations, providing they fit inside the free block (otherwise the heap has to be extended by asking the operating system to allocate more memory).

The garbage collector makes efforts to combine free space into large usable blocks by compacting generation 2. This is not always possible: for example an application may pin objects which will potentially prevent the garbage collector from combining free space by moving the live objects to the front of the heap. If this happens a lot, the application's memory will be broken up into useless small blocks and this affect is known as 'heap fragmentation'.

In addition, there is a Large Object Heap (LOH) in which larger objects are allocated. The rationale is that there is a cost associated with heap compaction as data must be copied around and so the LOH is not compacted, avoiding these costs. However, the flipside is that the LOH can become easily fragmented, with small, less useful blocks of free memory interspersed between live objects.

I would suggest running a dumpheap -stat. This command will report at the end of the list any fragmented blocks. You can then dump those blocks to get an idea of what is happening.

别念他 2024-09-22 15:11:00

顺便说一句,看起来你有一个众所周知的问题(至少在套接字专家中),大多数套接字服务器在.Net中都会遇到这个问题。 Paul 已经谈到了它的含义。更详细地说,问题是在套接字上读/写期间缓冲区被固定 - 这意味着 GC 不允许移动它(例如你的堆碎片)。 Coversant(该解决方案的先驱)在实际内存使用量仅为 500MB 左右(由于碎片如此严重)时遇到了 OutOfMemoryException。修复它完全是另一个故事。

你想要做的是在应用程序启动时分配大量的缓冲区(我目前正在做 50MB)。您会发现新的 ArraySegment (v2.0) 和 ConcurrentQueue (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 <--> DeflateStream <--> XmlWriter 等),您应该使用. 中的新套接字异步模式网络4.0;它在传递的 IAsyncResult 方面具有更高的效率。因为您没有对流进行分层,所以您可以完全控制所使用的缓冲区 - 因此您不需要走 NetworkStream 子类路线。

By the way, it looks like you have a well-known problem (at least among socket gurus) that most socket servers get in .Net. Paul has already touched on what it means. To elaborate more, what is going wrong is that during a Read/Write on the socket the buffer gets pinned - which means the GC isn't allowed to move it around (as such your heap fragments). Coversant (who pioneered the solution) were seeing an OutOfMemoryException when their actual memory usage was only about 500MB (due to such heavy fragmentation). Fixing it is another story entirely.

What you want to do is at application start up allocate a very large amount of buffers (I am currently doing 50MB). You will find the new ArraySegment<T> (v2.0) and ConcurrentQueue<T> (v4.0) classes particularly useful when writing this. There are a few lock-free queues floating around on the tubes if you are not using v4.0 yet.

// 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));
  }
}

Following this you will need to subclass NetworkStream and swap out the incoming buffer with one from your buffer pool. Buffer::BlockCopy will help performance (don't use Array::Copy). This is complicated and hairy; especially if you make it async-capable.

If you are not layering streams (e.g. SSLStream <--> DeflateStream <--> XmlWriter etc.) you should use the new socket async pattern in .Net 4.0; which has more efficiency around the IAsyncResults that get passed around. Because you are not layering streams you have full control over the buffers that get used - so you don't need to go the NetworkStream subclass route.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文