.NET 进程的内存使用量 = CLR 堆内存的 5 倍?

9

我正在尝试解决一些内存使用问题。总的来说,我的应用程序收集了一些数据值,并使用C1 WPF图表和数据网格进行可视化,最终将所有内容放入PDF报告中。

使用YourKit对我的进程进行分析后,我发现CLR堆大小约为120MB(这很好),而进程内存大小约为580MB。这几乎是实际CLR堆大小的5倍。我的CLR峰值大小为220MB,而进程内存分配为710MB。

我很清楚,在我的对象堆、堆栈等方面需要一些开销。在Java JVM中,我习惯于使用的典型因子大约为1.5x。

如何解释这种过度的内存开销?该进程是否只是分配了空闲的堆空间?如果是,这是否可以解释710MB与220MB之间的差异?

2个回答

16

这里还有一些额外的说明。虽然我不确定你所说的“CLR堆大小”具体是什么意思。 根据您使用的.NET运行时,CLR使用了8或9个不同的堆,因此在Heap Size与VM size之间看到的内存占用量只能解释部分差异:

  1. Loader Heap:包含CLR结构和类型系统
  2. High Frequency Heap:静态变量,MethodTables,FieldDescs,接口映射
  3. Low Frequency Heap:EEClass,ClassLoader和查找表
  4. Stub Heap:用于CAS、COM封装器、P/Invoke的桩
  5. Large Object Heap:需要大于85k字节的内存分配
  6. GC Heap:应用程序私有的用户分配堆内存
  7. JIT Code Heap:由mscoreee(执行引擎)和JIT编译器为托管代码分配的内存
  8. Process/Base Heap:交互操作/非托管分配,本地内存等。
  9. 在.NET 5中添加Pinned Object Heap (POH)

导致过多内存使用的其他两个原因是内存碎片化(主要发生在LOH或大对象堆上)或高数量的线程。

内存碎片化有很多原因,排除此原因的最佳方法是使用WinDbg分析GC Heap上每个段的段大小。

至于高线程数量,对于每个应用程序使用的线程,您拥有1MB(对于x86进程)或4MB(对于x64进程)的堆栈空间分配。这种内存放置在进程/基础堆中。因此,如果您有100个线程,您可以额外使用100MB / 400MB的内存使用量。

希望对您有所帮助


非常感谢您提供的宝贵和全面的笔记,特别是关于.NET内存分配解剖和潜在问题方面的内容。在我的情况下,最终发现是C1图形组件通过GDI+使用了大块内存。作为一名Java专家,我完全无法通过.NET内存分析工具看到这一点,感到非常困惑。最终,我们不得不通过限制使用那些C1图形组件来解决这个问题。 - bentolor
很高兴能够帮忙。我曾经使用WinDbg调试过许多内存转储,这让我学到了很多东西。在使用第三方组件时,我会关注两个方面:首先,确保它们被正确处理;其次,注意应用程序使用的句柄数量。可以通过使用TaskManager(确保Handles列可见)或SysInternals ProcessExplorer轻松完成此操作。通常,您会使用“using()”语句来确保及时处理组件。但是,我不熟悉WPF,因此这可能已经由框架处理了。 - Dave Black
本,你是怎么发现GDI+在消耗内存的? - RollRoll

2
如果托管堆的总大小明显小于应用程序使用的私有字节,则很可能您正在分配未受管理的内存并且(可能)没有正确处理它。实现IDisposable接口的图形对象、流和其他对象需要在超出范围之前调用其Dispose()方法或将其放置在using(){}语句中,以便清理任何未受管理的资源。使用像ANTS Memory Profiler这样的工具可以显示您的内存分配方式以及哪些对象实现了IDisposable接口。

谢谢你,丹!这正是我所缺失的指针。也许 ANTS 也不会在这里有所帮助(我已经在使用 YourKit),因为它无法跟踪非托管内存泄漏。目前我正在尝试使用 DebugDiag 1.2 解决我的问题。 - bentolor
我不熟悉DebugDiag,但是Smartbear的AQTime可以对托管和非托管代码进行分析。它不像ANTS那样易于使用,但提供更多的信息。请参见http://smartbear.com/products/qa-tools/application-performance-profiling。 - Dan Busha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接