Ответ
Исключение 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) лопается от бесконечной рекурсии. Это две большие разницы, как говорят в Одессе.