.NET 进程的内存转储中存在大量无法解释的内存
Large unexplained memory in the memory dump of a .NET process
我无法解释 C# 进程使用的大部分内存。总内存为 10 GB,但可访问和不可访问的对象总计为 2.5 GB。我想知道这 7.5 GB 可能是什么?
我正在寻找最可能的解释或方法来找出这段记忆可能是什么。
具体情况如下。该过程是.NET 4.5.1。它从 Internet 下载页面并使用机器学习对其进行处理。如 VMMap 所示,内存几乎完全位于托管堆中。这似乎排除了非托管内存泄漏。
进程已经 运行ning 好几天了,内存慢慢增长。在某些时候,内存为 11 GB。我在此过程中停止了所有 运行ning。我 运行 垃圾收集包括 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 并没有真正完全压缩
- 除了探查器显示的对象外,还使用了一些内存
经过调查,问题恰好是由于固定缓冲区导致的堆碎片。我将解释如何调查以及什么是固定缓冲区。
我用过的所有分析器都同意说大部分堆是免费的。现在我需要看看碎片化。例如,我可以用 WinDbg 做到这一点:
!dumpheap -stat
然后我查看了"Fragmented blocks larger than..."部分。 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层。当托管代码需要将缓冲区的地址发送到外部代码时,它需要 "pin" 缓冲区以便地址保持有效:GC 无法移动它。
这些缓冲区虽然只占内存的一小部分,但无法进行压缩,从而导致大量内存 "leak",即使这不完全是泄漏,更多的是碎片问题。这可能发生在 LOH 上,也可能发生在分代堆上。现在的问题是:是什么导致这些固定的对象永远存在:找到导致碎片的泄漏的根本原因。
您可以在这里阅读类似的问题:
https://ayende.com/blog/181761-C/the-curse-of-memory-fragmentation
.NET deletes pinned allocated buffer(答案中对固定对象的很好解释)
注意:根本原因在第三方库中 AerospikeClient using the .NET async Socket API that is known for pinning the buffers sent to it。虽然 AerospikeClient 正确使用了缓冲池,但在重新创建其客户端时会重新创建缓冲池。由于我们每小时重新创建一次他们的客户端,而不是永远创建一个,因此重新创建了缓冲池,导致固定缓冲区数量不断增加,进而导致无限碎片。仍然不清楚的是,为什么旧缓冲区在传输结束时或至少在其客户端被处置时永远不会取消固定。
我无法解释 C# 进程使用的大部分内存。总内存为 10 GB,但可访问和不可访问的对象总计为 2.5 GB。我想知道这 7.5 GB 可能是什么?
我正在寻找最可能的解释或方法来找出这段记忆可能是什么。
具体情况如下。该过程是.NET 4.5.1。它从 Internet 下载页面并使用机器学习对其进行处理。如 VMMap 所示,内存几乎完全位于托管堆中。这似乎排除了非托管内存泄漏。
进程已经 运行ning 好几天了,内存慢慢增长。在某些时候,内存为 11 GB。我在此过程中停止了所有 运行ning。我 运行 垃圾收集包括 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 并没有真正完全压缩
- 除了探查器显示的对象外,还使用了一些内存
经过调查,问题恰好是由于固定缓冲区导致的堆碎片。我将解释如何调查以及什么是固定缓冲区。
我用过的所有分析器都同意说大部分堆是免费的。现在我需要看看碎片化。例如,我可以用 WinDbg 做到这一点:
!dumpheap -stat
然后我查看了"Fragmented blocks larger than..."部分。 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层。当托管代码需要将缓冲区的地址发送到外部代码时,它需要 "pin" 缓冲区以便地址保持有效:GC 无法移动它。
这些缓冲区虽然只占内存的一小部分,但无法进行压缩,从而导致大量内存 "leak",即使这不完全是泄漏,更多的是碎片问题。这可能发生在 LOH 上,也可能发生在分代堆上。现在的问题是:是什么导致这些固定的对象永远存在:找到导致碎片的泄漏的根本原因。
您可以在这里阅读类似的问题:
https://ayende.com/blog/181761-C/the-curse-of-memory-fragmentation
.NET deletes pinned allocated buffer(答案中对固定对象的很好解释)
注意:根本原因在第三方库中 AerospikeClient using the .NET async Socket API that is known for pinning the buffers sent to it。虽然 AerospikeClient 正确使用了缓冲池,但在重新创建其客户端时会重新创建缓冲池。由于我们每小时重新创建一次他们的客户端,而不是永远创建一个,因此重新创建了缓冲池,导致固定缓冲区数量不断增加,进而导致无限碎片。仍然不清楚的是,为什么旧缓冲区在传输结束时或至少在其客户端被处置时永远不会取消固定。