System.OutOfMemoryException

An OutOfMemoryException is thrown when the runtime cannot allocate the memory that your code asks for. There are two very different cases. Either one allocation is too large to be possible, or the process has used all the memory that is available to it.

The first one is a bug in a single line, and easy to find. The second means a leak or a design that holds too much in memory, and takes more investigation.

Minimum version: >= 1.1 >= Core 1.0

Statistics

18
elmah.io logo 25

Common causes

Check the message and the line first. They tell you whether it is one big allocation or the total that is the problem.

A single allocation that is too large

An array has a maximum length, and a single object has a limit that depends on the platform and the settings. The message is Array dimensions exceeded supported range, and it can happen long before the machine is out of memory.

var buffer = new byte[int.MaxValue];   // OutOfMemoryException: Array dimensions exceeded supported range.

Process the data in chunks, or as a stream, and keep a buffer of a fixed size:

var path = Path.GetTempFileName();
using var stream = File.OpenRead(path);
var buffer = new byte[81920];
int read;

while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
    // process buffer[0..read]
}

Reading a whole file or query result into a list or a string works with small data and fails when the data grows. Building a big string with += in a loop makes it worse, because each step allocates a new copy.

var all = File.ReadAllText(Path.GetTempFileName());   // the whole file in memory

var report = "";
foreach (var line in File.ReadLines(Path.GetTempFileName()))
{
    report += line;   // a new string for every line
}

Stream the data, and use StringBuilder when you have to build text:

var builder = new System.Text.StringBuilder();

foreach (var line in File.ReadLines(Path.GetTempFileName()))
{
    builder.AppendLine(line);
}

Objects that stay reachable are never collected. Static collections and caches without a limit, event handlers that are never removed, and long-lived objects that keep references to short-lived ones are the usual reasons. The memory grows with time or load, until the process fails.

public static class PriceCache
{
    private static readonly Dictionary<string, byte[]> Cache = new();

    public static void Add(string key, byte[] value) => Cache[key] = value;   // never removed
}

Use a cache with limits and expiry, such as MemoryCache with a size limit, and unsubscribe from events when you are done:

var cache = new MemoryCache(
    new MemoryCacheOptions { SizeLimit = 1024 });

cache.Set("prices", new byte[10], new MemoryCacheEntryOptions
{
    Size = 1,
    SlidingExpiration = TimeSpan.FromMinutes(5)
});

A 32-bit process can use about 2 GB, and a container with a memory limit is stopped at its limit. Memory fragmentation of the large object heap can also make a big allocation fail with free memory left.

Run as a 64-bit process, check the memory limit of the container, and look at the numbers in a monitoring tool before you change code.

How to fix it and prevent it

Stream instead of loading everything

Read files line by line with File.ReadLines, use IAsyncEnumerable for large results, and page database queries. The memory use then depends on the size of one item, not the total.

foreach (var line in File.ReadLines(Path.GetTempFileName()))
{
    Process(line);
}

If the memory grows over time, you need to see what is in it. Use dotnet-gcdump or dotnet-dump, or the memory tools of Visual Studio, and look at which types take up most of the space and what keeps them alive.

Code that allocates large arrays for each request puts pressure on the large object heap. ArrayPool<T> gives you buffers that you return after use.

var pool = System.Buffers.ArrayPool<byte>.Shared;
var buffer = pool.Rent(1024 * 1024);

try
{
    // use the buffer
}
finally
{
    pool.Return(buffer);
}

How to read the stack trace

The trace shows which line asked for the memory when it failed:

System.OutOfMemoryException: Array dimensions exceeded supported range.
   at Shop.Services.ReportBuilder.Allocate(Int32 size) in C:\src\Shop\Services\ReportBuilder.cs:line 9
   at Program.<Main>$(String[] args) in C:\src\Shop\Program.cs:line 4

The message Array dimensions exceeded supported range says that the single array was too large, so the problem is the line itself: ReportBuilder.cs line 9. If the message instead is Insufficient memory to continue the execution of the program, the line in the trace is only where the last allocation failed, and not the cause. Look at the memory use of the process over time.

Should you catch it?

Rarely. When a single large allocation fails, you can catch it and return an error to the user, for example for a file that is too big. When the process is out of memory, almost any code can fail next, and the best you can do is log and let the process restart.

Check sizes before you allocate when the size comes from a user or a file, so you never get here.

Find OutOfMemoryException before your users do

elmah.io logs every unhandled exception in your .NET application with its stack trace and request details, groups identical errors and notifies you when a new one appears.

Start free trial
Free 21-day trial No credit card required

Related exceptions

Frequently asked questions

Why do I get OutOfMemoryException when there is free memory?

The allocation is bigger than the largest array or object that is allowed, or the memory is too fragmented to find a block that is large enough. The process can also run under a limit that is smaller than the machine.

Take a dump or a heap snapshot at two points in time and compare them. dotnet-gcdump, dotnet-dump and Visual Studio show which types grow and which roots keep them alive.

It only frees objects that nothing refers to. A static list, a cache or an event handler that holds a reference keeps the object alive, so the memory is never released.

No. If the memory is in use, a collection does not free it, and forced collections hurt performance. Fix what holds on to the memory.

Let your AI agent track it down

Connect Claude Code, Cursor, VS Code or Visual Studio to the elmah.io MCP server and ask your agent to look into OutOfMemoryException for you. For example:

AI chat
Find the most frequent OutOfMemoryException in my production log and show me the line that throws it.

The agent reads the stack trace and request details from elmah.io, finds the code in your repository and proposes a fix. In Claude Code, add the server with one command:

claude mcp add --transport http --client-id claudecode elmahio https://mcp.elmah.io/mcp

The MCP server is included on every plan and is currently in beta. Set up the MCP server.

Further reading

YouTube videos

Answers from Stack Overflow

You may want to read this: "“Out Of Memory” Does Not Refer to Physical Memory" by Eric Lippert.

In short, and very simplified, "Out of memory" does not really mean that the amount of available memory is too small. The most common reason is that within the current address space, there is no contiguous portion of memory that is large enough to serve the wanted allocation. If you have 100 blocks, each 4 MB large, that is not going to help you when you need one 5 MB block.

Key Points:

  • the data storage that we call “process memory” is in my opinion best visualized as a massive file on disk.
  • RAM can be seen as merely a performance optimization
  • Total amount of virtual memory your program consumes is really not hugely relevant to its performance
  • "running out of RAM" seldom results in an “out of memory” error. Instead of an error, it results in bad performance because the full cost of the fact that storage is actually on disk suddenly becomes relevant.

By Fredrik M&#246;rk. Read the original answer on Stack Overflow.

Each character requires 2 bytes (as a char in .NET is a UTF-16 code unit). So by the time you've reached 800 million characters, that's 1.6GB of contiguous memory required1. Now when the StringBuilder needs to resize itself, it has to create another array of the new size (which I believe tries to double the capacity) - which means trying to allocate a 3.2GB array.

I believe that the CLR (even on 64-bit systems) can't allocate a single object of more than 2GB in size. (That certainly used to be the case.) My guess is that your StringBuilder is trying to double in size, and blowing that limit. You may be able to get a little higher by constructing the StringBuilder with a specific capacity - a capacity of around a billion may be feasible.

In the normal course of things this isn't a problem, of course - even strings requiring hundreds of megs are rare.


1 I believe the implementation of StringBuilder actually changed in .NET 4 to use fragments in some situations - but I don't know the details. So it may not always need contiguous memory while still in builder form... but it would if you ever called ToString.

By Jon Skeet. Read the original answer on Stack Overflow.

Check that you are building a 64-bit process, and not a 32-bit one, which is the default compilation mode of Visual Studio. To do this, right click on your project, Properties -> Build -> platform target : x64. As any 32-bit process, Visual Studio applications compiled in 32-bit have a virtual memory limit of 2GB.

64-bit processes do not have this limitation, as they use 64-bit pointers, so their theoretical maximum address space (the size of their virtual memory) is 16 exabytes (2^64). In reality, Windows x64 limits the virtual memory of processes to 8TB. The solution to the memory limit problem is then to compile in 64-bit.

However, object’s size in .NET is still limited to 2GB, by default. You will be able to create several arrays whose combined size will be greater than 2GB, but you cannot by default create arrays bigger than 2GB. Hopefully, if you still want to create arrays bigger than 2GB, you can do it by adding the following code to you app.config file:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>

By Shift Technology. Read the original answer on Stack Overflow.

You don't have a continuous block of memory in order to allocate 762MB, your memory is fragmented and the allocator cannot find a big enough hole to allocate the needed memory.

  1. You can try to work with /3GB (as others had suggested)
  2. Or switch to 64 bit OS.
  3. Or modify the algorithm so it will not need a big chunk of memory. maybe allocate a few smaller (relatively) chunks of memory.

By Shay Erlichmen. Read the original answer on Stack Overflow.

IMAGE_FILE_LARGE_ADDRESS_AWARE is only relevant for 32 bit processes. The reason is that the address space on 32 bit Windows is split in two: 2 GB for kernel space and 2 GB for user space. To address 2 GB you need 31 bits. I.e. the pointers in a 32 bit application do not need the last bit for addressing.

Some applications may have used this extra bit for custom purposes, so if the Windows memory manager suddenly hands them a real 32 bit address they can't handle that. By enabling the IMAGE_FILE_LARGE_ADDRESS_AWARE flag the application basically tells the OS that it can handle the entire 32 bit addressable space.

If you run a IMAGE_FILE_LARGE_ADDRESS_AWARE application on 32 bit Windows you can access 3 GB. If you run the same 32 bit application on 64 bit Windows the process actually gets the entire 4 GB address space.

If you run a 64 bit application on 64 bit Windows the user address space is 8 TB (with another 8 TB set aside for kernel address space). .NET applications set to AnyCPU will automatically be 64 bit applications on x64, so you don't have to do anything to address the additional memory.

Keep in mind, however, that the CLR imposes a 2 GB limit on any single object, so while your application may use a lot of memory, you cannot create a 2 TB array for instance. More info in this question: Single objects still limited to 2 GB in size in CLR 4.0?

By Brian Rasmussen. Read the original answer on Stack Overflow.