Россия, Челябинск, Южно-Уральский Государственный Университет (НИУ) |
Управление памятью и сборка мусора
Поколения объектов
Упомянем еще одну распространенную оптимизацию, применяемую при сборке мусора: учет поколений объектов. Практические исследования показали, что в современных языках программирования активность объекта зависит от его возраста, причем нетривиальным образом: чем старше объект, тем больше его ожидаемое время жизни. Подавляющее большинство элементов программы используются сугубо локально, чаще всего все время жизни переменной в программе (от описания до последнего использования) умещается в пределы одного блока, процедуры или метода. Кроме того, типичный объект связан, в основном, со своими "сверстниками" и чаще всего умирает вместе с ними. Исходя из этого, логично предположить, что сборка мусора будет наиболее эффективной при обработке недавно созданных объектов. Действительно, большинство объектов утилизируются при первом же вызове сборки мусора.
Поэтому в целях уменьшения времени работы современные алгоритмы сборки мусора специально помечают объекты, пережившие сборку мусора. Такие объекты объединяются в поколения : к нулевому объекту относят объекты, не пережившие еще ни одной сборки мусора, к первому поколению - объекты, пережившие одну сборку мусора и т.д. В дальнейшем сборщик мусора постарается ограничиться обработкой только нулевого поколения (например, во время разметки памяти сборщик мусора не перебирает указатели из "старых" объектов). Если при этом высвобождено достаточно памяти для дальнейшей работы, то сборка мусора на том прекращается, если же нет, то производится сборка мусора в первом поколении, затем при необходимости во втором и т.д.
Алгоритм выделения памяти в .NET
Основными механизмом работы с памятью в .NET являются неявное управления памятью, стековый механизм выделения памяти и сборка мусора, использующая поколения и механизм "разметки-и-уплотнения" (mark-and-compact). Сразу оговоримся, что платформа .NET содержит также средства, позволяющие обойти эти ограничения, например, C# содержит специальный оператор fixed и некоторые средства управления механизмом сборки мусора, но в данной лекции мы их проигнорируем.
Для начального выделения памяти простой стековый механизм: имеется один указатель на следующее свободное место в куче, который после помещения в кучу очередного объекта увеличивается на его размер. Понятно, что в какой-то момент указатель кучи может выйти за пределы доступного адресного пространства - в этот момент начинает работу алгоритм сборки мусора. В целях оптимизации процесса сборка мусора чаще всего ограничивается обходом нулевого поколения - чаще всего этого оказывается достаточно.
Для сборки мусора производится маркировка активных элементов; она начинается с так называемых корневых объектов, список которых хранится в JIT-компиляторе .NET и предоставляется сборщику мусора. По окончании маркировки все активные элементы сдвигаются к началу кучи путем простого копирования памяти. Так как эта операция компрометирует все указатели, сборщик мусора также исправляет все ссылки, используемые программой.
Реально алгоритм сборки мусора, используемый в .NET, существенно сложнее, так как включает в себя такие оптимизации как слабые ссылки, отдельную кучу для крупных объектов, сборку мусора в многопоточных приложениях и т.д. Подробное описание алгоритма сборки мусора .NET можно найти в статье Дж. Рихтера "Automatic Memory Management in the Microsoft .NET Framework", MSDN Magazine, Nov/Dec 2000.