Россия, Челябинск, Южно-Уральский Государственный Университет (НИУ) |
Управление памятью и сборка мусора
Неявное управление памятью
В последнее время в развитии языков программирования наблюдается отчетливая тенденция к усложнению средств управления памятью, требуемых при реализации транслятора, в частности, необходимость реализации сборки мусора. Параллельно с этим происходит полный или частичный отказ от предоставления программисту явных средств управления памятью.
Естественно, что у такого подхода есть как свои преимущества, так и свои недостатки. Среди недостатков следует особо выделить потенциальное замедление программ, использующих сборку мусора, что может отрицательно сказаться на приложениях, работающих в масштабе реального времени. Однако проблема нехватки памяти становится все менее острой по мере удешевления аппаратной памяти: большинство приложений попросту не будет испытывать потребности в дополнительной памяти. Это особенно приятно, так как такие приложения практически не несут никаких накладных расходов на управление памятью.
Кроме того, неявное управление памятью освобождает программиста от большого объема рутинной работы по отслеживанию занимаемой и возвращаемой памяти, так как все проблемы этого процесса "скрываются" от него компилятором. Это значит, что с программиста снимается ответственность за еще один системный вопрос, не относящийся напрямую к его основным задачам и он сможет больше времени посвящать собственно прикладным вопросам.
Приблизительно такой же подход к "скрытию чрезмерной сложности от программиста" обеспечил в свое время широкое распространение языков высокого уровня. Вряд ли переход на неявное управление памятью приведет к такому же резкому увеличению производительности, как при переходе с ассемблера на языки высокого уровня, но упрощение работы программиста должно быть заметным.
Неявное управление памятью
Другая аргументация в пользу неявного управления памятью связана с тем, что как уже говорилось выше, программист в любом случае не сможет управлять всей памятью целиком - трудно представить себе современного программиста, самостоятельно расставляющего точки возврата из процедур и распределяющего регистры под временные переменные. В то же время наличие одновременно двух механизмов управления памятью (системного и программистского) может существенно усложнить работу компилятора или привести к необходимости создания отдельных областей памяти и программ для поддержки явных механизмов управления памятью. В конечном итоге это приводит к уменьшению эффективности управления памятью в целом.
Итак, неявное управление памятью сегодня является наиболее популярным методом, но для справедливости отметим, что из этого не следует делать далеко идущих выводов. Теория и практика языков программирования активно развиваются, и то, что находится в забвении сегодня, может стать популярным завтра. Так, в середине 70-х годов большинство исследователей было уверено, что неявное управление памятью окончательно вытеснило все остальные методы. Единственным представителем явного управления памятью был уже не очень популярный PL/I с оператором ALLOCATE . При этом стоимость неявного управления казалась уже вполне приемлемой.
Однако с появлением языка С и резким нарастанием его популярности ситуация кардинально изменилась. Во главу угла стали ставить эффективность и предоставление программисту широких возможностей по влиянию на системные процессы. В результате, системы со сборкой мусора в течение ближайших 20-25 лет преимущественно оставались уделом академических исследователей. Только дальнейшее удешевление аппаратуры и желание избавиться от ошибок управления памятью помогли неявному управлению памятью возвратить себе позиции среди практических языков программирования.
Фазы управления памятью
Как мы уже говорили выше, действия любого метода управления памятью можно разделить на некоторые стандартные фазы. Мы будем выделять три крупных этапа:
- Начальное выделение памяти
- Утилизация памяти
- Уплотнение и повторное использование
Во время начального распределения памяти необходимо разметить всю память как свободную либо используемую в каких-то целях. Кроме того, необходимо каким-то образом учитывать свободную память. Далее, система должна обнаруживать фрагменты памяти, которые были использованы, но уже стали ненужными. Такие фрагменты подлежат утилизации для дальнейшего повторного использования. Утилизация может быть как простой (в тех случаях, когда освобождаемые участки памяти смежны и непрерывны), так и достаточно сложной (в тех случаях, когда участки освобождаются в различных областях памяти или в случайном порядке). Наконец, память должна быть повторно использована. Для этого, возможно, потребуется применение механизмов уплотнения свободной памяти в целях создания нескольких больших блоков свободной памяти из множества маленьких.
Можно выделить три основных метода управления памятью (мы приводим их в порядке возрастания сложности реализации):
- Статическое распределение памяти
- Стековое распределение памяти
- Представление памяти в виде кучи (heap)
Ранее многие языки проектировались в расчете на какой-то один их этих механизмов управления памятью. Сегодня большинство языков программирования требуют от компиляторов использования всех трех механизмов управления памятью - обычно подразумевается применение самого "дешевого" из пригодных способов. В дальнейшем