У меня есть служба 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 действительно появляется через пару часов работы. Также перехватывается исключение, и служба продолжает работать.