C#: исключение из памяти


сегодня мое приложение сегодня бросил OutOfMemoryException. Для меня это всегда было почти невозможно, так как у меня есть 4 ГБ оперативной памяти и много виртуальной памяти тоже. Ошибка произошла, когда я попытался добавить существующую коллекцию в новый список.

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);  

насколько я понимаю, здесь не так много памяти, так как транспортные средства, которые должен содержать мой новый список, уже существуют в памяти. Я должен признать Vehicle - это очень сложный класс, и я попытался добавить около 50.000 элементы в новый список однажды. Но так как все Vehicles в приложении исходят из базы данных, размер которой составляет всего 200 МБ: я понятия не имею, что может вызвать OutOfMemoryException на данный момент.

10 54

10 ответов:

две точки:

  1. если вы используете 32-битную Windows, у вас не будет всех доступных 4 ГБ, только 2 ГБ.
  2. Не забывайте, что основная реализация List - это массив. Если ваша память сильно фрагментирована, может быть недостаточно смежного пространства для выделения вашего List, хотя в общей сложности у вас есть много свободной памяти.

3-летняя тема, но я нашел другое рабочее решение. Если вы уверены, что у вас достаточно свободной памяти, работает 64-разрядная ОС и все еще получает исключения, обязательно установите этот параметр в свойствах проекта enter image description here

.Net4.5 больше не имеет ограничения на 2 ГБ для объектов. Добавьте эти строки в приложение.конфигурации

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

и можно будет создавать очень большие объекты без получения OutOfMemoryException

обратите внимание, что он будет работать только на x64 ОС!

данные, хранящиеся в базе данных по сравнению с памятью в ваше приложение очень разные.

нет способа получить точный размер вашего объекта, но вы можете сделать это:

GC.GetTotalMemory() 

после загрузки определенного количества объектов и посмотреть, сколько ваша память меняется при загрузке списка.

Если это список, который вызывает чрезмерное использование памяти, то мы можем посмотреть на способы его минимизации. Например, почему вы хотите загрузить 50 000 объектов память все сразу в первую очередь. Не лучше ли позвонить в БД, как вы их требуете?

Если вы посмотрите здесь:http://www.dotnetperls.com/array-memory вы также увидите, что объекты в .NET больше, чем их фактические данные. Общий список-это даже больше боров памяти, чем массив. Если у вас есть общий список внутри вашего объекта, то он будет расти еще быстрее.

OutOfMemoryException (на 32-битных машинах) так же часто о фрагментации, как и фактические жесткие ограничения на память - вы найдете много об этом, но вот мой первый хит google кратко обсуждает это:http://blogs.msdn.com/b/joshwil/archive/2005/08/10/450202.aspx. (@Anthony Pegram ссылается на ту же проблему в своем комментарии выше).

тем не менее, есть еще одна возможность, которая приходит на ум для вашего кода выше: как вы используете "IEnumerable" конструктор в список, вы мая не давая объекту никаких подсказок относительно размера коллекции, которую вы передаете конструктору списка. Если передаваемый объект не является коллекцией (не реализует ICollection интерфейс), то за кулисами реализации списка будет нужно расти несколько (или много) раз, каждый раз оставляя слишком маленький массив, который должен быть собран мусор. Сборщик мусора, вероятно, не доберется до этих отброшенных массивов достаточно быстро, и вы получите свою ошибку.

самым простым решением для этого было бы использовать List(int capacity) конструктор, чтобы сообщить фреймворку, какой размер резервного массива выделить (даже если вы оцениваете и просто угадываете "50000", например), а затем используйте AddRange(IEnumerable collection) метод для фактического заполнения вашего списка.

Итак, самое простое "исправление", если я прав: заменить

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);

С

List<Vehicle> vList = new List<Vehicle>(50000);  
vList.AddRange(selectedVehicles);

все остальные комментарии и ответы по-прежнему применяются в отношении общие дизайнерские решения-но это может быть быстрым решением.

Примечание (как @Alex прокомментировал ниже), это только проблема, если selectedVehicles не является ICollection.

моя команда разработчиков разрешила эту ситуацию:

мы добавили в него следующий скрипт после сборки .exe-проект и снова скомпилирован, установив цель на x86 и увеличив ее на 1,5 Гб, а также на платформу x64, увеличивающую память с использованием 3,2 ГБ. Наше приложение 32 бит.

Связанных С URL-Адресами:

сценарий:

if exist "$(DevEnvDir)..\tools\vsvars32.bat" (
    call "$(DevEnvDir)..\tools\vsvars32.bat"
    editbin /largeaddressaware "$(TargetPath)"
)

вы не должны пытаться принести весь список сразу, te размер элементов в базе данных не то же самое, что тот, который он принимает в память. Если вы хотите обрабатывать элементы, вы должны использовать a для каждого цикла и использовать преимущества ленивой загрузки entity framework, чтобы вы не вносили все элементы в память сразу. Если вы хотите показать список, используйте разбиение на страницы (.Скакать и. возьмите ())

в то время как GC сжимает небольшую кучу объектов в рамках стратегии оптимизации для устранения дыр в памяти, GC никогда не сжимает большую кучу объектов по соображениям производительности**(стоимость уплотнения слишком высока для больших объектов (более 85 КБ в размере))**. Следовательно, если вы запускаете программу, которая использует много больших объектов в системе x86, вы можете столкнуться с исключениями OutOfMemory. Если вы используете эту программу в системе x64, у вас может быть фрагментированная куча.

Я знаю, что это старый вопрос, но поскольку ни один из ответов не упоминал большую кучу объектов, это может быть полезно для других, кто найдет этот вопрос ...

любое выделение памяти в .NET, которое превышает 85 000 байт, происходит из кучи больших объектов (LOH), а не из обычной кучи небольших объектов. Почему это так важно? Потому что большая куча объектов не уплотняется. Это означает, что большая куча объектов фрагментируется, и по моему опыту это неизбежно приводит к нехватке памяти ошибки.

в исходном вопросе список содержит 50 000 элементов. Внутренне список использует массив и предполагает 32 бит, который требует 50 000 x 4bytes = 200 000 байт (или вдвое больше, если 64 бит). Так что выделение памяти происходит из кучи больших объектов.

Так что вы можете поделать?

Если вы используете версию .net до 4.5.1, то все, что вы можете сделать, это знать о проблеме и попытаться ее избежать. Итак, в данном случае, вместо того, чтобы иметь список транспортных средств, вы можете иметь список списков транспортных средств, если в нем никогда не было более 18 000 элементов. Это может привести к некоторому уродливому коду, но это жизнеспособная работа вокруг.

Если вы используете .net 4.5.1 или более позднюю версию, то поведение сборщика мусора изменилось незначительно. Если вы добавите следующую строку, где вы собираетесь сделать большие выделения памяти:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;

оно принудит сборщик отброса компактировать большое куча объектов-только в следующий раз.

возможно, это не лучшее решение, но для меня сработало следующее:

int tries = 0;
while (tries++ < 2)
{
  try 
  {
    . . some large allocation . .
    return;
  }
  catch (System.OutOfMemoryException)
  {
    System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
    GC.Collect();
  }
}

конечно, это помогает только в том случае, если у вас есть физическая (или виртуальная) память.

по мере развития .Net, так же как и их способность добавлять новые 32-разрядные конфигурации, которые, похоже, отключают всех.

Если вы находитесь на платформе .Net Framework 4.7.2, выполните следующие действия:

перейти к свойствам проекта

построить

снимите флажок 'prefer 32-bit'

Ура!