Исключение нехватки памяти, даже если кажется, что достаточно памяти

Иногда наши клиенты наблюдают исключение нехватки памяти в нашем приложении. Поскольку мы регистрируем их действия, мы можем приблизительно воспроизвести то, что они сделали, но если я сделаю это и профилирую приложение с помощью dotMemory, я не смогу воспроизвести исключение, и используемая память (около 100 МБ управляемых + 500 МБ неуправляемых) намного меньше предела (2 ГБ, так как это 32-битное приложение). Кроме того, в момент обнаружения исключения запрашивается текущее использование памяти с помощью Process.GetCurrentProcess().WorkingSet64, который указывает использование памяти между 500 и 900 МБ. Я знаю, что этот номер не очень надежен, но это еще один признак того, что памяти должно быть достаточно.

Соответствующим свойством приложения является то, что оно имеет дело с временными рядами измерений (пары DateTime и double, хранящиеся в массиве). Эти объекты могут быть достаточно большими для хранения в куче больших объектов (LOH). Таким образом, фрагментация кучи происходит, но при профилировании это не представляло особого труда. Размер LOH был менее 100 МБ, включая отверстия.

Может ли быть возможно, что сборщик мусора (GC) вызывается после возникновения исключения из-за нехватки памяти? Я думаю, что в случае неудовлетворенного запроса на выделение памяти исключение выдается только в том случае, если сборщик мусора не может собрать достаточно памяти. Но, может быть, это отличается для памяти, выделенной в LOH, по сравнению с памятью, выделенной в куче 0 поколения?

У кого-нибудь есть идеи, как мы могли бы решить эту проблему?

Мы используем VS 2010 SP1 и.NET 4.0. Эта проблема может быть связана с вопросом, поднятым здесь, здесь и здесь, но я не нашел там удовлетворительного ответа.

Обновление: добавлена ​​примерная трассировка стека и диаграмма фрагментации кучи

Не существует уникального места, где бы возникали исключения из нехватки памяти, но так как это было запрошено, я добавил трассировку:

Exception of type 'System.OutOfMemoryException' was thrown.
mscorlib
  at System.Runtime.Serialization.ObjectIDGenerator.Rehash()
  at System.Runtime.Serialization.ObjectIDGenerator.GetId(Object obj, Boolean& firstTime)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.InternalGetId(Object obj, Boolean assignUniqueIdToValueType, Type type, Boolean& isNew)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Schedule(Object obj, Boolean assignUniqueIdToValueType, Type type, WriteObjectInfo objectInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteMembers(NameInfo memberNameInfo, NameInfo memberTypeNameInfo, Object memberData, WriteObjectInfo objectInfo, NameInfo typeNameInfo, WriteObjectInfo memberObjectInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteMemberSetup(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, String memberName, Type memberType, Object memberData, WriteObjectInfo memberObjectInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, String[] memberNames, Type[] memberTypes, Object[] memberData, WriteObjectInfo[] memberObjectInfos)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
  at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
  at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
  ... <methods from our application follow>

На следующей диаграмме из dotMemory изображена фрагментация LOH после примерно часа работы с инструментом:

1 ответ

Решение

Используя инструмент vmmap, я нашел причину проблемы: фактическая память, доступная для управляемой кучи, намного меньше предела 2 ГБ. Несколько общих библиотек, загруженных для взаимодействия с инструментами MS Office (~400 МБ). Существуют также библиотеки с собственным кодом (~300 МБ), которые также выделяют неуправляемую кучу (~300 МБ). Есть также много других вещей, и, в конце концов, для управляемой кучи остается только около 700 МБ.

Поскольку доступной памяти намного меньше, чем я первоначально думал, фрагментация LOH может оказать большее влияние, чем я подозревал, и действительно: vmmap показывает, что самый большой свободный блок в этой области памяти с течением времени становится меньше, даже если доступная память остается прежней, Я думаю, это доказательство фрагментации является причиной проблемы. Триггером исключения часто является двоичная сериализация, которую мы иногда используем для глубокого копирования объектов. Кажется, это вызывает пик использования памяти.

Так что с этим делать? Я рассматриваю следующие варианты:

  • Переключиться на x64 (что произойдет в конце концов)
  • Переключитесь на.NET 4.5.1, который позволяет дефрагментировать LOH
  • Уменьшите общее использование памяти: похоже, что дефрагментация занимает больше времени, если дополнительно доступно около 200 МБ. Это происходит, когда некоторые большие библиотеки не загружены. В своих экспериментах я больше не мог вызывать исключение нехватки памяти.
  • Измените код, который, вероятно, будет слишком трудоемким
Другие вопросы по тегам