В чем причина OutOfMemoryException в .NET в службе Windows при большой нагрузке?

У меня есть служба Windows, которая занимается обслуживанием. Недавно мы добавили задание, которое пытается предварительно вычислить некоторые результаты поиска с помощью Lucene, и с тех пор оно началось создание исключений OutOfMemory (OOM).

Некоторые подробности, которые я получил от WinDbg и SOS:

0:034> !analyzeoom
Managed OOM occured after GC #176014 (Requested to allocate 2621440 bytes)
Reason: Low on memory during GC
Detail: SOH: Failed to reserve memory (16777216 bytes)

!dumpheap -stat результат команды (последние)

65fe4944    81900     34614564 System.Byte[]
65fe2938    76014     35904328 System.Int32[]
65f96064       74     39988372 System.Int64[]
65fdf9ac  3208118    150302932 System.String
00265090      363    247694656      Free
Total 9035539 objects

Таким образом, свободная память есть, но она фрагментируется, и все части меньше 16 МБ (сегмент, выделенный по умолчанию). Массив байтов, int и int64 хранится в Lucene Cache. Кэш активируется из-за запроса, использующего сортировку. Реализация кеша Lucene основана на WeakReferenceHashMap и поэтому должна очищаться сборщиком мусора в случае нехватки памяти.

Команда Heapstat

0:034> !heapstat
Heap             Gen0         Gen1         Gen2          LOH
Heap0         1643476      2689484    526084512    196389976

Free space:                                                 Percentage
Heap0              12           12    170262384     77432248SOH: 32% LOH: 39%

Дамп исключений из лог-файла выглядит так:

Quartz.Core.ErrorLogger - Job (DEFAULT.precalculate-similar-index threw an exception.
Quartz.SchedulerException: Job threw an unhandled exception. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
   at Lucene.Net.Search.FieldCacheImpl.LongCache.CreateValue(IndexReader reader, Entry entryKey) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldCacheImpl.cs:line 685
   at Lucene.Net.Search.FieldCacheImpl.Cache.Get(IndexReader reader, Entry key) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldCacheImpl.cs:line 240
   at Lucene.Net.Search.FieldCacheImpl.GetLongs(IndexReader reader, String field, LongParser parser) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldCacheImpl.cs:line 639
   at Lucene.Net.Search.FieldCacheImpl.LongCache.CreateValue(IndexReader reader, Entry entryKey) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldCacheImpl.cs:line 667
   at Lucene.Net.Search.FieldCacheImpl.Cache.Get(IndexReader reader, Entry key) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldCacheImpl.cs:line 240
   at Lucene.Net.Search.FieldCacheImpl.GetLongs(IndexReader reader, String field, LongParser parser) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldCacheImpl.cs:line 639
   at Lucene.Net.Search.FieldComparator.LongComparator.SetNextReader(IndexReader reader, Int32 docBase) in C:\Dev\Lucene.Net_2_9_2\src\Lucene.Net\Search\FieldComparator.cs:line 481

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

Мы не закрепляем никакие объекты, и кажется, что Lucene тоже, хотя команда !gcroot возвращает для некоторых объектов следующий результат:

DOMAIN(0025D260):HANDLE(Pinned):1f13ec:Root:  02393250(System.Object[]) - from !gcroot
ESP:16f2e4: sizeof(02393250) =    123436600 (   0x75b7e38) bytes (System.Object[]) - size of the pinned arrays of objects

Система: Windows Server 2008 R2, 32-разрядная версия
Всего выделенных байтов: ~950 МБ
Всего зарезервировано байт: ~ 1666 МБ (числа взяты из монитора производительности)

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


person Eugeniu Torica    schedule 22.01.2011    source источник
comment
Кэш Lucene (FieldCache) использует слабые ссылки на основе чтения открытых сегментов. Записи кэша не будут удалены сборщиком мусора до тех пор, пока соответствующие считыватели сегментов не будут закрыты.   -  person sisve    schedule 22.01.2011
comment
@Simon Svensson - Reader закрывается после каждой довольно короткой партии (4-5 сотен поисков).   -  person Eugeniu Torica    schedule 22.01.2011


Ответы (1)


Утечки не было, но из-за высокой загрузки ЦП и памяти сборщик мусора выдавал перехватываемые исключения System.OutOfMemoryException (OOM). Так что с одной стороны процесс продолжал работать, а с другой индекс не обновлялся.

По крайней мере мне удалось как-то снизить нагрузку на память и теперь система работает нормально.

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

person Eugeniu Torica    schedule 24.02.2011