русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

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


Дата добавления: 2015-06-12; просмотров: 2578; Нарушение авторских прав


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

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

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



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

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

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

Конечно, когда объект перемещается из одного места в другое, все ссылки, указывающие на него, должны быть изменены. Ссылки в стеке или в статическом хранилище переопределяются сразу, но могут быть и другие ссылки на этот объект, которые исправляются позже, во время очередного «прохода». Исправление происходит по мере нахождения ссылок.

Существует два фактора, из-за которых «копирующие сборщики» обладают низкой эффективностью. Во-первых, в системе существует две кучи, и вы «перелопачиваете» память то туда, то сюда между двумя отдельными кучами, при этом половина памяти тратится впустую. Некоторые JVM пытаются решить эту проблему, выделяя память для кучи небольшими порциями по мере необходимости, а затем просто копируя одну порцию в другую.


Второй вопрос — копирование. Как только программа перейдет в фазу стабильной работы, она обычно либо становится «безотходной», либо производит совсем немного «мусора». Несмотря на это, копирующий сборщик все равно не перестанет копировать память из одного места в другое, что расточительно. Некоторые JVM определяют, что новых «отходов» не появляется, и переключаются на другую схему («адаптивная» часть). Эта схема называется пометить-и-убрать (удалить), и именно на ней работали ранние версии виртуальных машин фирмы Sun. Для повсеместного использования вариант «пометить-и-убрать» чересчур медлителен, но, когда известно, что нового «мусора» мало или вообще нет, он выполняется быстро.

Схема «пометить-и-убрать» использует ту же логику — проверка начинается со стека и статического хранилища, после чего постепенно обнаруживаются все ссылки на «живые» объекты. Однако каждый раз при нахождении объект поме­чается флагом, но еще продолжает существование. «Уборка» происходит только после завершения процесса проверки и пометки. Все «мертвые» объекты при этом удаляются. Но копирования не происходит, и если сборщик решит «упаковать» фрагментированную кучу, то делается это перемещением объектов внутри нее.

Идея «остановиться-и-копировать» несовместима с фоновым процессом сборки мусора; в начале уборки программа останавливается. В литературе фирмы Sun можно найти немало заявлений о том, что сборка мусора является фоновым процессом с низким приоритетом, но оказывается, что реализации в таком виде (по крайней мере в первых реализациях виртуальной машины Sun) в действительности не существует. Вместо этого сборщик мусора от Sun начинал выполнение только при нехватке памяти. Схема «пометить-и-убрать» также требует остановки программы.

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

Существуют и другие способы ускорения работы в JVM. Наиболее важные — это действия загрузчика и то, что называется компиляцией «на лету» (Just-In-Time, JIT). Компилятор JIT частично или полностью конвертирует программу в «родной» машинный код, благодаря чему последний не нуждается в обработке виртуальной машиной и может выполняться гораздо быстрее. При загрузке класса (обычно это происходит при первом создании объекта этого класса) система находит файл .class, и байт-код из этого файла переносится в память. В этот момент можно просто провести компиляцию JIT для кода класса, но такой подход имеет два недостатка: во-первых, это займет чуть больше времени, что вместе с жизненным циклом программы может серьезно отразиться на производительности. Во-вторых, увеличивается размер исполняемого файла (байт-код занимает гораздо меньше места в сравнении с расширенным кодом JIT), что может привести к подкачке памяти, и это тоже замедлит программу. Альтернативная схема отложенного вычисления подразумевает, что код JIT компилируется только тогда, когда это станет необходимо. Иначе говоря, код, который никогда не исполняется, не компилируется JIT. Новая технология Java HotSpot, встроенная в последние версии JDK, делает это похожим образом с применением последовательной оптимизации кода при каждом его выполнении. Таким образом, чем чаще выполняется код, тем быстрее он работает.

 

Инициализация членов класса

Java иногда нарушает гарантии инициализации переменных перед их использованием. В случае с переменными, определенными локально, в методе, эта гарантия предоставляется в форме сообщения об ошибке. Скажем, при попытке ис­пользования фрагмента

void f() { int і;

і ++, // Ошибка - переменная і не инициализирована

}

вы получите сообщение об ошибке, указывающее на то, что переменная і не была инициализирована. Конечно, компилятор мог бы присваивать таким переменным значения по умолчанию, но данная ситуация больше похожа на ошибку программиста, и подобный подход лишь скрыл бы ее. Заставить программиста присвоить переменной значение по умолчанию — значит предотвратить ошибку в программе.

Если примитивный тип является полем класса, то и способ обращения с ним несколько иной. Как было показано в главе 2, каждому примитивному полю класса гарантированно присваивается значение по умолчанию. Следующая программа подтверждает этот факт и выводит значения:

//: initialization/InitialValues.java

// Вывод начальных значений, присваиваемых по умолчанию.

import static net.mindview.util.Print.*;

 

public class InitialValues {

boolean t;

char c;

byte b;

short s;

int i;

long l;

float f;

double d;

InitialValues reference;

void printInitialValues() {

print("Data type Initial value");

print("boolean " + t);

print("char [" + c + "]");

print("byte " + b);

print("short " + s);

print("int " + i);

print("long " + l);

print("float " + f);

print("double " + d);

print("reference " + reference);

}

public static void main(String[] args) {

InitialValues iv = new InitialValues();

iv.printInitialValues();

/* Тут возможен следующий вариант:

new InitialValues().printInitialValues();

*/

}

}

<spoiler text="Output:">

Data type Initial value

boolean false

char [ ]

byte 0

</spoiler> Присмотритесь — даже если значения явно не указываются, они автоматически инициализируются. (Символьной переменной char присваивается значение ноль, которое отображается в виде пробела.) По крайней мере, нет опасности случайного использования неинициализированной переменной.

Если ссылка на объект, определямая внутри класса, не связывается с новым объектом, то ей автоматически присваивается специальное значение null (ключевое слово Java).

 



<== предыдущая лекция | следующая лекция ==>
Условие «готовности» | Явная инициализация


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.006 сек.