Какие есть способы вызвать исключение OutOfMemoryException в C#?

Ответ

Исключение OutOfMemoryException возникает, когда среде выполнения CLR не удается выделить новую память в управляемой куче. Вот типичные сценарии, которые могут к этому привести:

1. Непрерывное выделение больших массивов или объектов

Самый прямой способ — быстрое и неограниченное создание объектов, с которым сборщик мусора (GC) не успевает справиться.

// Пример 1: Создание больших массивов
List<byte[]> memoryHog = new List<byte[]>();
try
{
    while (true)
    {
        // Выделяем блок по 100 МБ
        memoryHog.Add(new byte[100 * 1024 * 1024]);
    }
}
catch (OutOfMemoryException ex)
{
    Console.WriteLine($"Out of Memory: {ex.Message}");
}

2. Утечки памяти через долгоживущие ссылки

Объекты, на которые сохраняются ссылки, никогда не собираются GC. Частая причина — статические коллекции.

public static class MemoryLeak
{
    // Статическая коллекция живет всё время работы домена приложения
    private static List<byte[]> _globalCache = new List<byte[]>();

    public static void CauseLeak()
    {
        // Каждый добавленный массив никогда не будет удален
        for (int i = 0; i < 10000; i++)
        {
            _globalCache.Add(new byte[10 * 1024 * 1024]); // 10 МБ
        }
    }
}

3. Фрагментация Large Object Heap (LOH)

Объекты размером >= 85 000 байт размещаются в LOH. LOH не компактифицируется по умолчанию (до .NET 4.5.1), что может привести к фрагментации и невозможности найти непрерывный блок памяти для нового большого объекта, даже если свободной памяти в сумме много.

// Чередование больших и маленьких объектов может фрагментировать LOH
var largeObjects = new List<byte[]>();
for (int i = 0; i < 1000; i++)
{
    // Создаем большой объект
    largeObjects.Add(new byte[85000]);
    // Создаем много маленьких объектов, которые умирают и оставляют "дыры"
    var temp = new byte[100];
}
// В какой-то момент выделение нового большого массива может не удасться
byte[] failedAllocation = new byte[85000]; // Возможен OutOfMemoryException

4. Ограничения процесса и среды

  • 32-битные приложения: Имеют ограничение адресного пространства в ~2 ГБ (или ~3-4 ГБ с флагом LARGEADDRESSAWARE).
  • 64-битные приложения: Теоретически ограничены объемом физической памяти и файла подкачки, но на практике могут столкнуться с ограничениями среды выполнения или фрагментацией.

Важное отличие: OutOfMemoryException относится к куче (heap). StackOverflowException — это отдельное исключение, связанное с переполнением стека вызовов (stack), обычно из-за бесконечной или слишком глубокой рекурсии.

Ответ 18+ 🔞

Ну, вот смотри, как это бывает на практике. Сидишь ты такой, пишешь код, всё вроде летает, а потом — бац! — и приложение твоё накрывается медным тазом с OutOfMemoryException. А всё почему? Потому что память — она не резиновая, её, блядь, надо уважать!

Вот основные способы, как можно довести свою программу до ручки, чтобы она начала материться на тебя этим исключением.

1. Жадность до памяти — это порок

Самый тупой и прямой способ — просто начать хапать память, как будто завтра не наступит. Создаёшь объекты пачками, а сборщик мусора (GC) за тобой просто не успевает. Он, бедолага, выдохся уже, а ты всё пихаешь и пихаешь.

// Пример 1: Жадный уёбок, который не знает меры
List<byte[]> memoryHog = new List<byte[]>();
try
{
    while (true) // Ну, классический бесконечный цикл, что может пойти не так?
    {
        // А давай-ка откусим по 100 МБ за раз! Щас нажрёмся!
        memoryHog.Add(new byte[100 * 1024 * 1024]);
    }
}
catch (OutOfMemoryException ex)
{
    Console.WriteLine($"Ну всё, пиздец, память кончилась: {ex.Message}");
}

2. Вечная жизнь объектов, или почему статика — это зло

Запомни раз и навсегда: если ты запихнул что-то в статическую коллекцию, то это что-то будет жить там вечно, как душа грешника в аду. Оно никогда не умрёт, не очистится, а будет тихо и методично жрать твою оперативку.

public static class MemoryLeak
{
    // Вот эта сука — статическая коллекция. Она переживёт апокалипсис.
    private static List<byte[]> _globalCache = new List<byte[]>();

    public static void CauseLeak()
    {
        // Каждый раз, вызывая этот метод, ты хоронишь ещё 10 МБ памяти. Навсегда.
        for (int i = 0; i < 10000; i++)
        {
            _globalCache.Add(new byte[10 * 1024 * 1024]); // Прощай, память!
        }
    }
}

3. Большая куча объектов (LOH) и её фрагментация — адская головоломка

Есть такая штука — Large Object Heap (LOH). Туда попадают все объекты размером от 85 000 байт. Проблема в том, что эта куча, особенно в старых версиях .NET, не умеет нормально "уплотняться". Представь, что ты пытаешься поставить шкаф в комнате, где валяется куча мелкого хлама. Места вроде много, но сплошного куска для шкафа — нет! Вот и тут так же.

// Создаём бардак в LOH
var largeObjects = new List<byte[]>();
for (int i = 0; i < 1000; i++)
{
    // Ставим "шкаф" (большой объект)
    largeObjects.Add(new byte[85000]);
    // Накидываем вокруг "мелкий хлам" (маленькие объекты), который потом уберут, но дырки останутся
    var temp = new byte[100];
}
// И в один прекрасный момент новый "шкаф" уже не влезает
byte[] failedAllocation = new byte[85000]; // Ёпта, OutOfMemoryException!

4. Аппаратные и системные ограничения — стена, в которую можно упереться

Тут всё просто:

  • 32-битное приложение: Максимум может увидеть где-то 2-4 ГБ памяти. Всё, что сверху — уже не его. Как ни пыжься.
  • 64-битное приложение: Теоретически может хапнуть овердохуища памяти. Но на практике упрётся или в физический объём RAM, или в файл подкачки, или опять же во фрагментацию.

И главное, не путай! OutOfMemoryException — это когда куча (heap) переполнилась. А есть ещё StackOverflowException — это когда стек вызовов (stack) лопается от бесконечной рекурсии. Это две большие разницы, как говорят в Одессе.