Восстановление памяти является сравнительно несложным процессом. Выполнение кода временно прекращается, а активные (live) объекты (то есть объекты, к которым ваш код может осуществлять непосредственный или косвенный доступ) отыскиваются и рекурсивно помечаются. Так как остальные объекты, находящиеся в памяти, уже недоступны активному коду, они остаются непомеченными, что позволяет идентифицировать их как "мусор" и восстановить соответствующую память. Такая разновидность сборки мусора известна под названием "отслеживание и очистка" ("mark and sweep"). Обычно эта операция осуществляется довольно быстро.
Возможность уплотнения объектов в памяти является дополнительным преимуществом выполнения управляемого кода. В этом случае, в отличие от собственных кодов, все известные ссылки на ваши объекты известны и исполнительному механизму. Благодаря этому можно перемещать объекты из одного места памяти в другое, если в этом возникает потребность. Диспетчер памяти (исполнительный механизм) отслеживает, где именно находятся те или иные объекты, и перемещает их в случае необходимости. Это позволяет уплотнять расположение набора управляемых объектов, разбросанных в памяти.
Однако в рукаве у .NET Compact Framework наряду с JIT-компилятором, диспетчером памяти и сборщиком мусора спрятан еще один козырь: возможность очищать память от пассивного (dead) JIT-компилированного кода. Обычно это делать нежелательно, но при определенных обстоятельствах такая возможность становится незаменимой. Если уж исполнительный механизм, обеспечивая максимальную производительность, потрудился и выполнил JIT-компиляцию управляемого кода до собственных команд процессора, то отбрасывание JIT-компилированного кода могло бы показаться расточительством, поскольку когда в следующий раз возникнет необходимость в выполнении этого кода, то потребуется его повторная JIT-компиляция. В то же время, наличие такой возможности оборачивается благом в следующих двух случаях:
1. Когда приложение осуществило JIT-компиляцию и был выполнен большой объем кода, который в ближайшее время не будет вновь выполняться. Довольно часто ситуации подобного рода возникают в тех случаях, когда выполнение приложения происходит в несколько стадий. Вполне возможно, что код, запускаемый на начальной стадии выполнения приложения для инициализации или настройки объектов и данных, впоследствии повторно выполняться не будет. Если возникает необходимость в дополнительном распределении памяти, то имеется полный смысл использовать для этого память, в которой хранится данный код.
2. Когда набор хранящихся в памяти активных объектов становится настолько большим, что возникает риск аварийного завершения выполнения приложения, если для размещения дополнительных объектов, которые в соответствии с алгоритмом должны создаваться в ходе нормального выполнения приложения, не найдется достаточно большого объема свободной памяти. В этом случае исполнительный механизм будет вынужден вытеснять из памяти и периодически перекомпилировать необходимый приложению код лишь для того, чтобы приложение было в состоянии продолжать дальнейшее выполнение. Если другой возможной альтернативой является только прекращение выполнения приложения из-за нехватки памяти, то единственным решением может быть лишь очистка памяти от JIT-компилированного кода, даже если это означает дополнительные затраты времени на последующую повторную компиляцию этого кода.
Краткий обзор методов управления памятью и сборки мусора
Полезно разобраться в том, каким образом исполнительный механизм взаимодействует с кодом и объектами приложения в процессе его выполнения.
Приведенные ниже схематические диаграммы помогут нам проследить, как осуществляется управление памятью на различных стадиях выполнения приложения.
Рис. 3.2. Упрощенное схематическое представление состояния памяти приложения в процессе выполнения
Таблица 3.1. Условные графические обозначения, используемые на рис. 3.2, 3.3 и остальных рисунках
Фигура |
Описание |
Темно-серые овалы |
Используемые в настоящее время объекты (например, объект Obj12 на рис. 3.2). |
Белые овалы |
Пассивные объекты. Эти объекты (например, объект Obj6 на рис. 3.2) являются "мусором", к которому у приложения больше нет доступа и от которого можно избавиться с целью высвобождения дополнительной памяти. |
Темно-серые прямоугольники |
Описания классов и типов, которые были загружены в память по той причине, что к типам осуществлял доступ выполняющийся код. |
Внутренние прямоугольники |
Код, содержащийся в классах. (Код целиком находится внутри класса.) |
Темно-серые внутренние прямоугольники |
Код, подвергнутый JIТ-компиляции, поскольку метод был вызван хотя бы один раз (например, код Code1 класса ClassXYZ на рис.3.2). |
Светло-серые внутренние прямоугольники |
Код, который еще не подвергался JIТ-компиляции. По сравнению с прямоугольниками JIТ-компилированного кода эти прямоугольники имеют меньшие размеры, ибо еще не скомпилированные методы (например, метод Code4 класса XYZ на рис. 3.2) занимают в памяти очень мало места. |
Приведенную на рис. 3.2 схему можно прокомментировать следующим образом:
Читать дальше