System.OutOfMemoryException выбрасывается при наличии свободного места на виртуальной машине

Прежде чем спрашивать, я сделаю небольшой отказ от ответственности: да, я знаю о различиях между виртуальной памятью, физической памятью и рабочим набором. Все числа ниже относятся к виртуальной памяти.

Ситуация такова: у нас есть 32-разрядное приложение C #, которое импортирует библиотеки x86 C ++ (с множеством собственных зависимостей, поэтому переход на x64 в настоящее время не вариант). Приложение загружает большой набор данных через неуправляемый компонент, а затем пытается отобразить его (отчет).

Однако, когда набор данных особенно велик, при добавлении элемента в список выдается исключение OutOfMemory, как в коде ниже:

Здесь нет ничего удивительного. Однако что удивительно, так это тот факт, что у приложения все еще есть около 280 МБ свободной виртуальной машины.

При отладке чисто неуправляемых приложений (C ++) этого никогда не было - bad_alloc можно было получить только в том случае, если не осталось свободной виртуальной машины или если не было свободного фрагмента адресного пространства достаточного размера.

Отсюда вопрос - в чем может быть причина? Я понимаю, как бороться с этой проблемой - неуправляемые компоненты действительно потребляют много памяти, а также создают большую фрагментацию - но в чем причина столь раннего появления OutOfMemoryException?

Рассматриваемый код выглядит так:

List<Cell> r = new List<Cell>(cols);
for (int j = 0; j < cols; j++)
{
    r.Add(new CustomCell()); // The exception is thrown on this line
}

На момент исключения в списке (в одном случае) было 85 элементов, а его вместимость составляла 200 с чем-то (количество столбцов, как указано в конструкторе). Итак, исключение, скорее всего, произошло в распределении CustomCell. У объекта CustomCell много полей, но его размер определенно меньше 1 КБ. 280 Мбайт свободной памяти располагаются блоками от 64 Кбайт до 14 Мбайт, поэтому должно быть достаточно места для ее распределения.


person DarkWanderer    schedule 05.09.2013    source источник
comment
Может проблема в фрагментации памяти. У вас достаточно свободной памяти, но не хватает непрерывного блока, достаточно большого для новых данных.   -  person Alessandro D'Andria    schedule 05.09.2013
comment
Конечно, не так. Я исследовал память приложения с помощью утилиты VMMap от Sysinternals и Visual Studio - там были свободные куски размером до 14 Мб. Неудачная операция создавала объект размером менее 1 КБ.   -  person DarkWanderer    schedule 05.09.2013
comment
OutOfMemory exception is thrown upon adding an item to the list. что это за список?   -  person Sriram Sakthivel    schedule 05.09.2013
comment
Обновил вопрос куском кода.   -  person DarkWanderer    schedule 05.09.2013


Ответы (3)


были чанки до 14Мб бесплатно. Неудачная операция создавала объект размером менее 1 КБ.

14 МБ определенно близко к опасной зоне. То, как GC выделяет виртуальную машину, не имеет ничего общего с размером объекта. Куча GC создается из частей виртуальной машины, называемых «сегментами». При запуске программы размер сегмента составляет 2 МБ, но сборщик мусора динамически увеличивает выделение новых сегментов, когда программа использует много памяти. Очевидно, вы используете много памяти. Вы ничего не можете сделать, чтобы повлиять на выделение виртуальных машин или избежать фрагментации адресного пространства виртуальных машин.

Очевидно, вы слишком близки к пределу виртуальной машины для 32-разрядного процесса. Вам нужно будет либо радикально пересмотреть свой код, чтобы вы могли обходиться меньшими затратами. Или вам нужно включить 64-битную операционную систему в список обязательных компонентов. Что может предоставить 4 гигабайта адресного пространства виртуальной машины 32-разрядному процессу. Чтобы воспользоваться этим, вам понадобится дополнительный этап сборки, описанный в этом ответе.

person Hans Passant    schedule 05.09.2013
comment
Ну, это близко, но не объясняет. Как я уже сказал, у меня есть много кусков разного размера - всего 280 МБ - достаточно места для размещения блока 2 МБ. Вы уверены, что размер сегмента не 16 МБ? Если бы это было так, то это объяснило бы это, тогда - person DarkWanderer; 05.09.2013
comment
Вы, должно быть, пропустили динамически растущую часть ответа. Сначала это всего 2 МБ. Конечно, он может вырасти до 16 МБ. - person Hans Passant; 05.09.2013
comment
А вы пропустили 280 МБ часть :) Можете описать точный сценарий, когда это может произойти? - person DarkWanderer; 05.09.2013
comment
Хорошо, я нашел более подробное объяснение в другом месте. Вы по-прежнему правы на высоком уровне, поэтому я принимаю ваш ответ. - person DarkWanderer; 10.09.2013

РЕДАКТИРОВАТЬ

Написано до того, как вы добавили свой код. Похоже, вы уже это делаете:

List экземпляры имеют емкость. Если вы превышаете емкость, запускается алгоритм увеличения, который удваивает емкость списка.

//decompiled from List<T>
private void EnsureCapacity(int min)
{
  if (this._items.Length >= min)
    return;
  int num = this._items.Length == 0 ? 4 : this._items.Length * 2;
  if ((uint) num > 2146435071U)
    num = 2146435071;
  if (num < min)
    num = min;
  this.Capacity = num; //causes a copying of src array to a new array
}

Это может привести к неожиданному выделению памяти. Если вы можете предвидеть окончательный размер списка, разместите его заранее и избегайте дублирования:

var myList = new List<SomeType>(expectedCapacity)

person spender    schedule 05.09.2013

Вы также должны позаботиться о том, чтобы у вас 64-битное приложение, максимальный размер массива - 2 ГБ, проверьте, загружаете ли вы таблицу отчета в массив.

Дополнительная информация: OutOfMemoryException при объявлении большого массива

Это также может быть для вас вставкой: http://social.msdn.microsoft.com/Forums/en-US/1a12abaa-50bd-4d28-b3c1-9de06a1488e9/how-to-create-an-extremely-large-arrayobject-2-gb-without-using-jagged-array-

person Bassam Alugili    schedule 05.09.2013