Difference between actual OOM and 2GB object OOM?

1

I was wondering if there is some difference between the OOM exceptions thrown for an actual OOM (memory runs out), and the exception thrown when the 2GB object limit is hit?

I have the following code for causing the OOMs (no app.config changes, gcAllowVeryLargeObjects is by default set to false):

struct Data
{
    double a;
    double b;
}

// Causes OOM due to the 2GB object limit.
List<Data> a = new List<Data>(134217725);

// Causes OOM due to actual OOM.
List<Data[]> b = new List<Data[]>();
for (int i = 0; i < 13421772; i++)
{
    b.Add(new Data[134217724]);
}

Now, I've executed the code from Visual Studio, and I get the following exceptions:

  • 2GB object limit
System.OutOfMemoryException
  HResult=0x8007000E
  Message=Exception of type 'System.OutOfMemoryException' was thrown.
  Source=mscorlib
  StackTrace:
   at System.Collections.Generic.List`1..ctor(Int32 capacity) 
   at ConsoleApp1.Program.Main(String[] args)
  • actual OOM
System.OutOfMemoryException
  HResult=0x8007000E
  Message=Exception of type 'System.OutOfMemoryException' was thrown.
  Source=ConsoleApp1
  StackTrace:
   at ConsoleApp1.Program.Main(String[] args)

From here, it doesn't seem like there is a significant difference between the two exceptions (other than the stack trace/source).

On the other hand, I executed the exact same thing from LINQPad and got the following:

  • 2GB limit

LINQPad 2GB limit exception

  • actual OOM

LINQPad actual OOM exception

Executing RuntimeInformation.FrameworkDescription from both places results in .NET Framework 4.8.4341.0

My question is about detecting/differentiating between the two cases, although I am also curious as to why the error messages differ between the LINQPad and VS executions.

c#
exception
out-of-memory
asked on Stack Overflow Mar 5, 2021 by np_6

1 Answer

1

I can explain the difference between LinqPad and Visual Studio:

If you run x86 DEBUG and RELEASE .Net Framework 4.8 builds of the following code:

static void Main()
{
    try
    {
        List<Data> a = new List<Data>(134217725);
    }
    
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }

    try
    {
        List<Data[]> b = new List<Data[]>();

        for (int i = 0; i < 13421772; i++)
        {
            b.Add(new Data[134217724]);
        }
    }

    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
}

For the RELEASE build you get:

Array dimensions exceeded supported range.
Exception of type 'System.OutOfMemoryException' was thrown.

For the DEBUG build you get:

Exception of type 'System.OutOfMemoryException' was thrown.
Exception of type 'System.OutOfMemoryException' was thrown.

This implies that the LINQPAD version is RELEASE and the Visual Studio version is DEBUG.

So the answer is: Yes, there clearly is some difference, but:

  • Only the message differs
  • We should not rely on this never changing
  • It differs between DEBUG and RELEASE builds.

Aside:

On my PC, the DEBUG build of the test code above immediately throws the two OutOfMemoryException exceptions.

However, the RELEASE build quickly throws the first OutOfMemoryException, but it is several seconds before it throws the second exception. During this time, its memory usage increases (according to Task Manager).

So clearly there is some other difference under the hood, at least for .Net Framework 4.8. I haven't tried this with .Net 5 or .Net Core.

answered on Stack Overflow Mar 5, 2021 by Matthew Watson • edited Mar 5, 2021 by Matthew Watson

User contributions licensed under CC BY-SA 3.0