40

40


Простой пример использования данного подхода:

//• i ni ti ali zati on/Termi nati onCondi ti on java

// Использование finalize() для выявления объекта,

// не осуществившего необходимой финализации

class Book {

boolean checkedOut = false,

Book(boolean checkout) {

checkedOut = checkout,

}

void checklnO {

checkedOut = false;

}

public void finalizeO { if(checkedOut)

System out println("Ошибка. checkedOut"); // Обычно это делается так-

// Super.finalize(), // Вызов версии базового класса

}

}

public class TerminationCondition {

public static void main(String[] args) { Book novel = new Book(true); // Правильная очистка- novel.checkln(),

// Теряем ссылку, забыли про очистку new Book(true);

// Принудительная сборка мусора и финализация System gc().

}

} /* Output

Ошибка checkedOut

* ///•-

«Условие готовности» состоит в том, что все объекты Book должны быть «сняты с учета» перед предоставлением их в распоряжение сборщика мусора, но в методе main() программист ошибся и не отметил один из объектов Book. Если бы в методе finalize() не было проверки на условие «готовности», такую оплошность было бы очень сложно обнаружить.

Заметьте, что для проведения принудительной финализации был использо¬ван метод System.gc(). Но даже если бы его не было, с высокой степенью вероят¬ности можно сказать, что «утерянный» объект Book рано или поздно будет об¬наружен в процессе исполнения программы (в этом случае предполагается, что программе будет выделено столько памяти, сколько нужно, чтобы сборщик мусора приступил к своим обязанностям).

Обычно следует считать, что версия finalize() базового класса делает что-то важное, и вызывать ее в синтаксисе super, как показано в Book.finalize(). В дан¬ном примере вызов закомментирован, потому что он требует обработки исклю¬чений, а эта тема нами еще не рассматривалась.

Как работает сборщик мусора

Если ранее вы работали на языке программирования, в котором выделение мес¬та для объектов в куче было связано с большими издержками, то вы можете предположить, что и в Java механизм выделения памяти из кучи для всех дан¬ных (за исключением примитивов) также обходится слишком дорого. Однако в действительности использование сборщика мусора дает немалый эффект по ускорению создания объектов. Сначала это может звучать немного странно — освобождение памяти сказывается на ее выделении — но именно так работают некоторые JVM, и это значит, что резервирование места для объектов в куче Java не уступает по скорости выделению пространства в стеке в других языках.

Представтьте кучу языка С++ в виде лужайки, где каждый объект «застол¬бил» свой собственный участок. Позднее площадка освобождается для повтор¬ного использования. В некоторых виртуальных машинах Java куча выглядит совсем иначе; она скорее похоже на ленту конвейера, которая передвигается вперед при создании нового объекта. А это значит, что скорость выделения хра¬нилища для объекта оказывается весьма высокой. «Указатель кучи» просто пе¬редвигается вперед в «невозделанную» территорию, и по эффективности этот процесс близок к выделению памяти в стеке С++. (Конечно, учет выделенного пространства сопряжен с небольшими издержками, но их никоим образом нельзя сравнить с затратами, возникающими при поиске свободного блока в па¬мяти.)

Конечно, использование кучи в режиме «ленты конвейера» не может про¬должаться бесконечно, и рано или поздно память станет сильно фрагментиро- вана (что заметно снижает производительность), а затем и вовсе исчерпается. Как раз здесь в действие вступает сборщик мусора; во время своей работы он компактно размещает объекты кучи, как бы смещая «указатель кучи» ближе к началу «ленты», тем самым предотвращая фрагментацию памяти. Сборщик мусора реструктуризует внутреннее расположение объектов в памяти и по¬зволит получить высокоскоростную модель кучи для резервирования памяти.

Чтобы понять, как работает сборка мусора в Java, необходимо узнать, как устроены реализации сборщиков мусора (СМ) в других системах. Простой, но медленный механизм СМ называется подсчетом ссылок. С каждым объектом хранится счетчик ссылок на него, и всякий раз при присоединении новой ссыл¬ки к объекту этот счетчик увеличивается. Каждый раз при выходе ссылки из области действия или установке ее значения в null счетчик ссылок уменьша¬ется. Таким образом, подсчет ссылок создает небольшие, но постоянные из¬держки во время работы вашей программы. Сборщик мусора перебирает объект за объектом списка; обнаружив объект с нулевым счетчиком, он освобождает ресурсы, занимаемые этим объектом. Но существует одна проблема — если объ¬екты содержат циклические ссылки друг на друга, их счетчики ссылок не обну¬ляются, хотя на самом деле объекты уже являются «мусором». Обнаружение таких «циклических» групп является серьезной работой и отнимает у сборщика мусора достаточно времени. Подсчет ссылок часто используется для объясне¬ния принципов процесса сборки мусора, но, судя по всему, он не используется ни в одной из виртуальных машин Java.

В более быстрых схемах сборка мусора не зависит от подсчета ссылок. Вме¬сто этого она опирается на идею, что любой существующий объект прослежива¬ется до ссылки, находящейся в стеке или в статической памяти. Цепочка про¬верки проходит через несколько уровней объектов. Таким образом, если начать со стека и статического хранилища, мы обязательно доберемся до всех исполь¬зуемых объектов. Для каждой найденной ссылки надо взять объект, на который она указывает, и отследить все ссылки этого объекта; при этом выявляются другие объекты, на которые они указывают, и так далее, пока не будет провере¬на вся инфраструктура ссылок, берущая начало в стеке и статической памяти. Каждый объект, обнаруженный в ходе поиска, все еще используется в системе. Заметьте, что проблемы циклических ссылок не существует — такие ссылки просто не обнаруживаются, и поэтому становятся добычей сборщика мусора ав¬томатически.

В описанном здесь подходе работает адаптивный механизм сбора мусора, при котором JVM обращается с найденными используемыми объектами соглас¬но определенному варианту действий. Один из таких вариантов называется ос- тановить-и-копировать. Смысл термина понятен: работа программы временно приостанавливается (эта схема не поддерживает сборку мусора в фоновом ре¬жиме). Затем все найденные «живые» (используемые) объекты копируются из одной кучи в другую, а «мусор» остается в первой. При копировании объек¬тов в новую кучу они размещаются в виде компактной непрерывной цепочки, высвобождая пространство в куче {и позволяя удовлетворять заказ на новое хранилище простым перемещением указателя).