c# - CLR/从32位进程切换到64位进程后内存消耗高

标签 c# memory memory-management memory-leaks clr

我有一个基于 .NET Framework 4.5 (C#) 构建的后端应用程序(Windows 服务)。应用运行在Windows Server 2008 R2服务器上,内存为64GB。

由于存在依赖关系,我曾经将此应用程序编译为 32 位进程(将其编译为 x86)并使用/LARGEADDRESSAWARE 标志让应用程序在用户空间中使用超过 2GB 的内存。使用此配置,平均内存消耗(根据任务管理器中的“内存(专用工作集)”列)约为 300-400MB。

我需要 LARGEADDRESSAWARE 标志的原因,以及我将其更改为 64 位的原因,是虽然 300-400MB 是平均值,但偶尔这个应用会做一些涉及加载的事情大量数据进入内存(当你的内存不是很有限时,开发和管理这类东西要容易得多)。

最近(在删除那些 x86 native 依赖项之后),我将应用程序编译更改为“任何 CPU”,因此现在,在生产服务器上,它作为 64 位进程运行。从我进行此更改开始,平均内存消耗(根据任务管理器)达到了新的水平:3-4 GB,此时没有其他更改可以解释此行为更改。

以下是关于当前状态的一些额外事实:

  • 根据“#Bytes in all heaps”计数器,内存总量约为 600MB。

  • 当使用 WinDbg+SOS 调试进程时,!dumpheap -stat 显示大约有 250-300MB 空闲,但所有其他对象远远少于进程使用的内存总量。

  • 根据 GC 性能计数器,定期进行 Gen0 回收。事实上,“GC 时间百分比”计数器表明平均 10-20% 的时间花费在 GC 上(考虑到应用程序的性质,这是有道理的——大量的信息和数据结构分配用于很短的时间)。

  • 我在此应用中使用服务器 GC。

  • 服务器上没有内存问题。它使用大约 50-60% 的可用内存 (64GB)。

我的问题:

  • 为什么分配给进程的内存(根据任务管理器)与 CLR 堆的实际大小(进程中没有非托管代码可以解释这一点)之间存在巨大差异?

  • 与作为 32 位进程运行的同一进程相比,为什么 64 位进程占用更多内存?即使考虑到指针占用两倍的大小,也有很大的不同。

  • 我可以做些什么来降低内存消耗,或者更好地理解这个问题吗?

谢谢!

最佳答案

有几点需要考虑:

1) 您提到您正在使用服务器 GC 模式。在服务器 GC 模式下,CLR 为机器上的每个 CPU 核心创建一个堆,这样在服务器进程中进行更高效的多线程处理,例如Asp.Net 进程。每个堆有两个段:一个用于小对象,一个用于大对象。每个段以 4 GB 保留内存开始。基本上服务器 GC 模式试图在系统上使用更多内存来换取整体系统性能。

2) 当然,指针在 64 位上更大。

3) 由于堆更大,前台 Gen2 GC 在服务器 GC 模式下变得非常昂贵。所以 CLR 非常努力地减少前台 Gen2 GC 的数量,有时使用后台 Gen2 GC。

4) 根据使用情况,碎片可能成为一个真正的问题。我见过有 98% 碎片的堆(98% 的堆是空闲 block )。

要真正解决你的问题,你需要获取ETW trace + 内存转储,然后使用PerfView等工具进行详分割析。

关于c# - CLR/从32位进程切换到64位进程后内存消耗高,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25069582/

相关文章:

c - RHEL6 中线程的最大虚拟内存分配

c - 返回指向函数内声明的复合文字的指针是否安全,还是应该使用 malloc?

c# - Azure Function v2 在处理 StorageException 后总是返回内部服务器错误 500

sql - microsoft sql server Management Studio Express 将数据库存储在内存中吗?

c# - SQL metal to dbml,如何生成正确的外键列名

r - java.lang.OutOfMemoryError : GC overhead limit exceeded

c - 了解 return 语句之前的 free() 缓冲区

c - 使用结构池正确处理内存

c# - 单次序列化/反序列化大对象与多次序列化/反序列化小对象?

c# - 锁定静态字段