C# 虽然字符串的最大大小约为0.75GB,但Visual Studio显示了5GB的使用率和内存运行,为什么会有差异?

C# 虽然字符串的最大大小约为0.75GB,但Visual Studio显示了5GB的使用率和内存运行,为什么会有差异?,c#,memory,memory-management,out-of-memory,heap-memory,C#,Memory,Memory Management,Out Of Memory,Heap Memory,这段代码没有任何实际意义,我只是想看看会发生什么 据我所知,唯一保留的两个变量是(最终)大字符串和跟踪字符串长度的可忽略大小int 在我的机器上,字符串大约为0.75GB,此时会发生OutOfMemoryException。在这个阶段,VisualStudio显示了大约5GB的使用量。所以我想知道为什么会有差异 var initialText = "Test content"; var text = initialText; var length = text.Length; while (tr

这段代码没有任何实际意义,我只是想看看会发生什么

据我所知,唯一保留的两个变量是(最终)大字符串和跟踪字符串长度的可忽略大小int

在我的机器上,字符串大约为0.75GB,此时会发生
OutOfMemoryException
。在这个阶段,VisualStudio显示了大约5GB的使用量。所以我想知道为什么会有差异

var initialText = "Test content";
var text = initialText;
var length = text.Length;
while (true)
{
    try
    {
        var currentLength = text.Length;
        Console.WriteLine($"Current Length - {currentLength}");
        Console.WriteLine($"Current Size in GB - {System.Text.Encoding.UTF8.GetByteCount(text)/1024.0/1024.0/1024.0}");
        text = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(text));
        Console.WriteLine($"Change In Size - {currentLength / (length + 0.0)}");
        length = currentLength;
    }
    catch (OutOfMemoryException)
    {
        break;
    }
}

作为第二个问题,当我开始运行代码时,根据Task Manager,我的机器有大约11GB的空闲空间,当它遇到异常时,它会增加大约3GB,这与上面的数字不符。有什么想法吗?

首先,.net中的字符串是一个UTF-16字序列,因此每个字符需要2个字节。要获得内存中字符串的字节大小,需要将其长度乘以2(忽略CLR实例头)

另一个限制是.NET中的数组大小。您可以达到两个限制:最大大小(2GB)和最大索引

以下是测试的修改版本:

var text = "Test content";
long length = text.Length;
try
{

    while (true)
    {
        var currentLength = text.Length;
        Console.WriteLine($"Current Length - {currentLength}");
        Console.WriteLine($"Current Size in GB - {text.Length * 2.0 / 1024 / 1024 / 1024}");
        text += new string('a', 500 * 1024*1024);
        length = currentLength;
        GC.Collect();
    }
}
catch (OutOfMemoryException e)
{
    Console.WriteLine(e);
}
StringBuilder
版本差异:

var text = new StringBuilder("Test content");
...
text.Append('a', 500 * 1024*1024);
如果不启用,则会得到1B元素的OOM

我无法使用字符串连接获得2B个元素,但如果使用
StringBuilder
重新编写此测试,则可以获得2B个字符。在本例中,您将遇到第二个限制:数组不能容纳超过20亿个元素。是关于上限的讨论

讨论了最大字符串长度

如果在
Release
模式下运行此代码,您将看到进程内存消耗几乎等于控制台输出中的字符串大小


另一件我注意到但无法解释的有趣事情是,使用
StringBuilder
gcAllowVeryLargeObjects
Debug
模式,我可以达到4GB,但在
Release
模式下,它几乎达到3GB。欢迎评论为什么会这样:)

啊,很有趣。我放置
GC.Collect()在循环结束时,它的行为与没有它时相同。你知道这和你的评论有什么关系吗?你不能可靠地测试连接了调试器的内存管理。您应该在没有调试器的情况下以发布模式运行应用程序,但使用内存分析工具来获得有效结果。我现在从命令行运行发布版本,它给我的行为与从Visual Studio以调试模式运行它的行为相同。我添加的额外命令是
GC.Collect()
GC.WaitForPendingFinalizers()
GC.WaitForFullGCComplete()。谢谢你的回答,顺便说一句,读起来真的很有趣:)在.net中有一个基本的数组大小,这对于32位和64位应用程序是不同的。不管是谁点击它,即您的代码还是您调用的代码,如果点击它,您将得到OutOfMemoryException。操作系统给了.net多少钱,以及.net管理了多少钱,这并不是问题所在。简言之,要了解您的问题,只需创建一个越来越大的数组(任何类型)。代码的其余部分只是噪音
var text = new StringBuilder("Test content");
...
text.Append('a', 500 * 1024*1024);