【问题标题】:Large unexplained memory in the memory dump of a .NET process.NET 进程的内存转储中有大量无法解释的内存
【发布时间】:2018-11-17 10:22:50
【问题描述】:

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

我正在寻找最可能的解释或方法来找出这段记忆可能是什么。

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

进程已经运行了好几天,内存慢慢增长。在某些时候,内存为 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 并未真正完全压缩
  • 在分析器显示的对象之外使用了一些内存

【问题讨论】:

  • 这相当于“发生内存泄漏,请修复。”。你的服务是做什么的?它会调用任何不受管理的东西吗?它使用 COM 吗?
  • GC 后不应该有那么多无法访问的对象。死锁的终结器线程导致内存爆炸,使用 WinDbg 看看它在做什么。
  • 谢谢汉斯。这段遥不可及的记忆对我来说确实很可疑。我去看看。
  • 听起来像是非托管内存使用。 JetBrains 分析器可以证明这一点。您也可以使用免费的 VMMap.exe 来测试该理论。
  • 谢谢你。我检查了VMMap。我的 10 GB 中有 95% 位于托管堆中。这似乎排除了非托管内存的使用。

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


【解决方案1】:

经过调查,问题恰好是由于固定缓冲区导致的堆碎片。我将解释如何调查以及固定缓冲区是什么。

我使用过的所有分析器都同意说大部分堆是免费的。现在我需要看看碎片。例如,我可以用 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 正确使用了缓冲池,但在重新创建客户端时重新创建了缓冲池。由于我们每小时重新创建他们的客户端而不是永远创建一个,缓冲池被重新创建,导致越来越多的固定缓冲区,进而导致无限碎片。尚不清楚的是为什么旧缓冲区在传输结束时或至少在其客户端被释放时从未取消固定。

【讨论】:

  • 谢谢你!!!我们有一个非常相似的案例 - 一个 ASP.NET 应用程序,它通过 .NET 内置 WebClient 从 Web 下载内容并进行一些非常轻量级的处理。一段时间后,该过程会消耗 3-4 GB。这篇文章对我的调查很有帮助
猜你喜欢
  • 1970-01-01
  • 2011-03-21
  • 2021-03-30
  • 2011-10-22
  • 2014-05-27
  • 1970-01-01
  • 1970-01-01
  • 2015-09-05
  • 2012-11-21
相关资源
最近更新 更多