.NET进程的内存转储中的大量无法解释的内存

时间:2018-11-17 10:22:51

标签: c# memory-profiling large-object-heap

我无法解释C#进程使用的大部分内存。总内存为10 GB,但可访问和不可访问的对象总数总计2.5 GB。我想知道这7.5 GB是什么?

我正在寻找最可能的解释或一种方法来找出此内存可以是什么。

这是确切的情况。该过程是.NET 4.5.1。它从互联网下载页面并通过机器学习对其进行处理。如VMMap所示,内存几乎完全位于托管堆中。这似乎可以排除非托管内存泄漏。 enter image description here

该进程已经运行了几天,并且内存缓慢增长。在某些时候,内存为11 GB。我停止所有正在运行的程序。我多次运行垃圾收集,包括large object heap compaction(间隔为一分钟):

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();

内存下降到10 GB。然后创建转储:

  

procdump -ma psid

转储为预期的10 GB。

我使用.NET memory profiler(版本5.6)打开转储。转储显示总共2.2 GB的可访问对象和0.3 GB的不可达对象。 如何解释剩余的7.5 GB?

我一直在想的可能解释:

  • LOH并没有完全压缩
  • 一些内存超出了探查器显示的对象

1 个答案:

答案 0 :(得分:2)

调查后,问题出在由于固定了缓冲区的堆碎片。我将解释如何调查以及什么是固定缓冲区。

我曾经使用过的所有探查器都同意说大部分堆都是免费的。现在,我需要查看碎片。我可以使用WinDbg做到这一点,例如:

!dumpheap -stat

然后,我查看了“大于...的碎片块”部分。 WinDbg说对象位于空闲块之间,因此无法进行压缩。然后,我查看了持有这些对象的对象以及它们是否被固定,例如,这里的对象位于地址0000000bfaf93b80:

!gcroot 0000000bfaf93b80

它显示参考图:

00000004082945e0 (async pinned handle)
-> 0000000535b3a3e0 System.Threading.OverlappedData
-> 00000006f5266d38 System.Threading.IOCompletionCallback
-> 0000000b35402220 System.Net.Sockets.SocketAsyncEventArgs
-> 0000000bf578c850 System.Net.Sockets.Socket
-> 0000000bf578c900 System.Net.SocketAddress
-> 0000000bfaf93b80 System.Byte[]

00000004082e2148 (pinned handle)
-> 0000000bfaf93b80 System.Byte[]

最后两行告诉您对象已固定。

固定对象是不能移动的缓冲区,因为它们的地址与非托管代码共享。在这里您可以猜到它是系统TCP层。当托管代码需要将缓冲区的地址发送给外部代码时,它需要“固定”缓冲区,以便该地址保持有效:GC无法移动它。

这些缓冲区虽然仅占内存的一小部分,但无法进行压缩,因此会导致较大的内存“泄漏”,即使这并非完全是泄漏,也会带来碎片问题。这可以在LOH或世代相同的堆上发生。现在的问题是:是什么导致这些固定的对象永远存在:找到导致碎片的泄漏的根本原因。

您可以在此处阅读类似的问题:

注意:根本原因是使用AerospikeClient的.NET异步套接字API在第三方库known for pinning the buffers sent to it中。尽管AerospikeClient正确使用了缓冲池,但在重新创建其客户端时会重新创建该缓冲池。由于我们不是每小时创建一个客户端,而是每个小时重新创建一个客户端,因此重新创建了缓冲池,从而导致固定缓冲区的数量不断增加,从而导致了无限的碎片。尚不清楚的是,为什么在传输结束时或至少在处置客户端时,永远不会取消固定旧缓冲区。