Запуск приложения.NET на Windows Server 2008 x64 с 16 ГБ RAM. Это приложение должно выбрать и проанализировать очень большой объем данных (приблизительно 64 ГБ) и сохранить все это в памяти когда-то.
Что я ожидаю видеть: размер Процесса расширяется на прошлых 16 ГБ до 64 ГБ. Windows использует виртуальную память для подкачки страниц дополнительных данных к/от диску по мере необходимости. Это - классический вариант использования виртуальной памяти.
Что я на самом деле вижу: размер Процесса ограничен на сумму физической памяти (16 ГБ). Приложение тратит 99,8% своего времени в сборщике "мусора".
Почему нашему приложению не удается использовать виртуальную память? Действительно ли это - проблема в конфигурации сборщика "мусора".NET, или в самом диспетчере виртуальной памяти Windows x64? Что я могу сделать, чтобы заставить наше приложение использовать виртуальную память, а не ограничиваться физической памятью?
Спасибо.
- Brian
Обновление: Я записал очень небольшую программу, которая показывает то же поведение:
using System;
namespace GCTest
{
class Program
{
static void Main()
{
byte[][] arrays = new byte[100000000][];
for (int i = 0; i < arrays.Length; ++i)
{
arrays[i] = new byte[320];
if (i % 100000 == 0)
{
Console.WriteLine("{0} arrays allocated", i);
System.Threading.Thread.Sleep(100);
}
}
}
}
}
Если Вы хотите попробовать его, удостоверьтесь, что создали для x64. Вам, вероятно, придется изменить константы немного для выделения системы. Поведение, которое я вижу, состоит в том, что процесс срывает, поскольку он приближается к размеру 16 ГБ. Нет никакого сообщения об ошибке или выданного исключения. Монитор производительности сообщает, что % процессорного времени в GC приближается к 100%.
Разве это не недопустимо? Где система виртуальной памяти?
Вы проверили, что ваш файл подкачки настроен так, что он может расширяться до такого размера?
Обновление
Я немного поигрался с этим на вашем примере, и вот что я вижу.
Система: Windows 7 64bit, 6 ГБ трехканальной оперативной памяти, 8 ядер.
Вам нужен дополнительный файл подкачки на другом шпинделе вашей ОС, иначе подобное расследование приведет к тому, что ваша машина будет работать как шланг. Если все борются за один и тот же файл подкачки, это ухудшает ситуацию.
Я наблюдаю большое количество данных, перемещаемых из поколения в поколение в GC, плюс большое количество GC sweeps\collections, и огромное количество ошибок страниц в результате, когда достигаются пределы физической памяти. Я могу только предположить, что когда физическая память исчерпана до предела, это вызывает зачистку и продвижение поколений, вызывая тем самым обращение к большому количеству выгружаемой памяти, что приводит к смертельной спирали, поскольку выгружаемая память заполняется, а другая память вытесняется. Все это заканчивается беспорядочной работой. Это кажется неизбежным при выделении большого количества долгоживущих объектов, которые оказываются в Small Object Heap.
Теперь сравним это с выделением объектов в моде, которая выделяет их непосредственно в Large Object Heap (которая не страдает теми же проблемами сметания и продвижения):
private static void Main()
{
const int MaxNodeCount = 100000000;
const int LargeObjectSize = (85 * 1000);
LinkedList<byte[]> list = new LinkedList<byte[]>();
for (long i = 0; i < MaxNodeCount; ++i)
{
list.AddLast(new byte[LargeObjectSize]);
if (i % 100000 == 0)
{
Console.WriteLine("{0:N0} 'approx' extra bytes allocated.",
((i + 1) * LargeObjectSize));
}
}
}
Это работает как ожидалось, т.е. виртуальная память используется и в конечном итоге исчерпывается - 54GB в моей среде\configuration.
Таким образом, похоже, что выделение массы долгоживущих мелких объектов в конечном итоге приведет к порочному циклу в GC, так как генерация очисток и продвижений производится, когда физическая память исчерпана - это спираль смерти страничного файла.
Обновление 2
В процессе изучения проблемы я играл с рядом опций\конфигураций, которые не дали ощутимой разницы:
Похоже, вы не храните ссылку на большие данные. Сборщик мусора не будет собирать объекты, на которые имеются ссылки.