c# - 当计算机有更多内存时,为什么 .net 程序使用更多 VirtualMemorySize64
问题描述
我创建了一个简单的测试应用程序,它使用二进制数组分配 100mb。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace VirtualMemoryUsage
{
class Program
{
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"IsServerGC = {System.Runtime.GCSettings.IsServerGC.ToString()}");
const int tenMegabyte = 1024 * 1024 * 10;
long allocatedMemory = 0;
List<byte[]> memory = new List<byte[]>();
for (int i = 0; i < 10; i++)
{
//alloc 10 mb memory
memory.Add(new byte[tenMegabyte]);
allocatedMemory += tenMegabyte;
}
sb.AppendLine($"Allocated memory: {PrettifyByte(allocatedMemory)}");
sb.AppendLine($"VirtualMemorySize64: {PrettifyByte(Process.GetCurrentProcess().VirtualMemorySize64)}");
sb.AppendLine($"PrivateMemorySize64: {PrettifyByte(Process.GetCurrentProcess().PrivateMemorySize64)}");
sb.AppendLine();
Console.WriteLine(sb.ToString());
Console.ReadLine();
}
private static object PrettifyByte(long allocatedMemory)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
int order = 0;
while (allocatedMemory >= 1024 && order < sizes.Length - 1)
{
order++;
allocatedMemory = allocatedMemory / 1024;
}
return $"{allocatedMemory:0.##} {sizes[order]}";
}
}
}
注意:对于这个测试,在 app.config 中将 gcserver 设置为 true 很重要
<runtime>
<gcServer enabled="true"/>
</runtime>
这将显示进程分配的 PrivateMemorySize64 和 VirtualMemorySize64 的数量。
虽然 PrivateMemorySize64 在不同的计算机上保持相似,但 VirtualMemorySize64 变化很大。
当分配相同数量的内存时,VirtualMemorySize64 出现这种差异的原因是什么?有这方面的文件吗?
解决方案
哇,你真幸运。在我的机器上,最后一行显示 17 GB!
Allocated memory: 100M
VirtualMemorySize64: 17679M
PrivateMemorySize64: 302M
虽然 PrivateMemorySize64 在不同的计算机上保持相似 [...]
私有字节是仅属于您的程序的字节。它几乎不受其他事物的影响。它包含您堆上的内容,其他人无法访问。
为什么是 302 MB 而不仅仅是 100 MB?SysInternals VMMap 是分解该值的好工具:
私有字节的颜色和大小说:
- 紫色 (7.5 MB):图像文件,即不可共享的 DLL
- 橙色 (11.2 MB):堆(非 .NET)
- 绿色 (103 MB):托管堆
- 橙色 (464 kB):堆栈
- 黄色 (161 MB):私有数据,例如 TEB 和 PEB
- 棕色 (36 MB):页表
如您所见,.NET 在托管堆中只有 3 MB 开销。剩下的就是任何流程都需要做的其他事情。
调试器或分析器可以帮助分解托管堆:
0:013> .loadby sos clr
0:013> !dumpheap -stat
[...]
000007fedac16878 258 11370 System.String
000007fed9bafb38 243 11664 System.Diagnostics.ThreadInfo
000007fedac16ef0 34 38928 System.Object[]
000007fed9bac9c0 510 138720 System.Diagnostics.NtProcessInfoHelper+SystemProcessInformation
000007fedabcfa28 1 191712 System.Int64[]
0000000000a46290 305 736732 Free
000007fedac1bb20 13 104858425 System.Byte[]
Total 1679 objects
所以你可以看到.NET“默认”需要一些字符串和其他对象。
当分配相同数量的内存时,VirtualMemorySize64 出现这种差异的原因是什么?
0:013> !address -summary
[...]
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 58 7fb`adfae000 ( 7.983 TB) 99.79%
MEM_RESERVE 61 4`3d0fc000 ( 16.954 GB) 98.11% 0.21%
MEM_COMMIT 322 0`14f46000 ( 335.273 MB) 1.89% 0.00%
仅提交了 335 MB。那是实际可以使用的内存。16.954 GB 只是保留。目前无法使用它们。它们既不在 RAM 中,也不在页面文件的磁盘中。分配保留内存非常快。我经常看到 17 GB 的值,尤其是在 ASP.NET 故障转储中。
再次查看VMMap中的细节
我们可以看到 17 GB 只是分配在一个块中。对您的问题的评论说:“当系统内存不足时,垃圾收集器会触发并释放繁忙的垃圾收集器。” 但是,要通过 VirtualFree() 释放 VirtualAlloc() 的块,该块在逻辑上不能为空,即内部不应有单个 .NET 对象 - 这不太可能。所以它会永远留在那里。
有哪些可能的好处?它是一个连续的内存块。如果你new byte[4G]()
现在需要,它就可以工作。
最后,可能的原因是:它完成了,因为它没有伤害,无论是 RAM 还是磁盘。并且在需要时,它可以在稍后的时间点提交。
有这方面的文件吗?
这不太可能。详细的 GC 实现可能会随着 .NET 的下一个版本而改变。我认为微软没有记录这一点,否则如果行为改变,人们会抱怨。
有些人写过这样的博客文章,告诉我们某些值可能取决于处理器的数量。
0:013> !eeheap -gc
Number of GC Heaps: 4
[...]
我们在这里看到的是,.NET 创建的堆与处理器一样多。这对于垃圾收集很有用,因为每个处理器都可以独立收集一个堆。
推荐阅读
- java - 在 shadowroot 内部的 Modal 内部初始化 Autocomplete 不起作用
- google-apps-script - 授权非管理员用户在 Google Apps 脚本中运行 Admin SDK
- angular - 使用 typeahead ngx-bootstrap 合并两个调用并显示结果?
- flutter - 提示文本和文本在 textformfield 中具有不同的 maxline
- python - 如何在 Django admin 中隐藏(不禁用)ModelAdmin 列表视图中的操作添加模型按钮?
- node.js - 使用旋转文件流暂停/停止文件旋转
- amazon-web-services - AWS CloudFormation:Cognito LambdaTrigger CustomEmailSender - 属性“AWS CloudFormation 目前不支持。” 和 CDK 的使用
- python - 从 sqlite3 数据库中检索保存的生成密码到 gen。登录时输入密码,但我卡住了
- github - GitHub 上下文变量未评估可重用工作流参考
- python - 突出显示散点图上的单个点