5. ⭐ Устройство JVM. Сборщик мусора (GC)
⭐ За что отвечает JVM:
- Загрузка, проверка и исполнение байт кода;
- Предоставление среды выполнения для выполнения байт-кода;
- Управление памятью и очисткой мусора (Garbage collection);
Виртуальная машина Java (Java Virtual Machine) - это механизм, предоставляющий среду выполнения для управления Java-кодом или приложениями. Виртуальная машина является независимой оболочкой исполнения кода, благодаря которой возможен её запуск на любой ОС, без влияния ОС на выполняемую программу.
Рисунок 1. Архитектура JVM
JVM работает с 2мя типами данных: примитивные типы (primitive types) и ссылочные типы (reference types).
Примитивы
JVM работает с примитивными значениями (целыми числами и числами с плавающей точкой). По сути, JVM - это 32-битная машина.
Типы long
и double
, которые являются 64-битными, поддерживаются изначально, но занимают две единицы памяти в frame's local
или стеке операндов, поскольку каждая единица составляет 32 бита.
Типы boolean
, byte
, short
и char
имеют расширенный знак (кроме char
с нулевым расширением) и работают как 32-разрядные целые числа, так же, как и типы int
.
Меньшие типы имеют только несколько специфических для типа инструкций для загрузки, хранения и преобразования типов.
boolean
значение работает как 8-битное byte
значения, где 0 представляет значение false, а 1 - значение true.
Типы ссылок и значения
Существует три типа ссылочных типов: типы классов, типы массивов и типы интерфейсов. Их значения являются ссылками на динамически создаваемые экземпляры классов, массивы или экземпляры классов, которые реализуют интерфейсы соответственно.
Classloader
Загрузчик классов является частью JRE, которая динамически загружает Java классы в JVM. Обычно классы загружаются только по запросу. Система исполнения в Java не должна знать о файлах и файловых системах благодаря загрузчику классов. Делегирование является важной концепцией, которую выполняет загрузчик. Загрузчик классов отвечает за поиск библиотек, чтение их содержимого и загрузку классов, содержащихся в библиотеках. Эта загрузка обычно выполняется «по требованию», поскольку она не происходит до тех пор, пока программа не вызовет класс. Класс с именем может быть загружен только один раз данным загрузчиком классов.
При запуске JVM, используются три загрузчика классов:
- Bootstrap class loader (Загрузчик класса Bootstrap)
- Extensions class loader (Загрузчик класса расширений)
- System class loader (Системный загрузчик классов)
Загрузчик класса Bootstrap загружает основные библиотеки Java, расположенные в папке <JAVA_HOME>/jre/lib
.
Этот загрузчик является частью ядра JVM, написан на нативном коде.
Загрузчик класса расширений загружает код в каталоги расширений
(<JAVA_HOME>/jre/lib/ext
, или любой другой каталог, указанный системным свойством java.ext.dirs
).
Системный загрузчик загружает код, найденный в java.class.path
, который сопоставляется с переменной среды CLASSPATH
.
Это реализуется классом sun.misc.Launcher$AppClassLoader
.
Загрузчик классов выполняет три основных действия в строгом порядке:
- Загрузка: находит и импортирует двоичные данные для типа.
- Связывание: выполняет проверку, подготовку и (необязательно) разрешение.
- Проверка: обеспечивает правильность импортируемого типа.
- Подготовка: выделяет память для переменных класса и инициализация памяти значениями по умолчанию.
- Разрешение: преобразует символические ссылки из типа в прямые ссылки.
- Инициализация: вызывает код Java, который инициализирует переменные класса их правильными начальными значениями.
Пользовательский загрузчик классов
Загрузчик классов написан на Java. Поэтому возможно создать свой собственный загрузчик классов, не понимая тонких деталей JVM. У каждого загрузчика классов Java есть родительский загрузчик классов, определенный при создании экземпляра нового загрузчика классов или в качестве системного загрузчика классов по умолчанию для виртуальной машины.
Что делает возможным следующее:
- загружать или выгружать классы во время выполнения (например, динамически загружать библиотеки во время выполнения, даже из ресурса HTTP).
Это важная особенность для:
- реализация скриптовых языков;
- использование bean builders;
- добавить пользовательскую расширение;
- позволяя нескольким пространствам имен общаться. Например, это одна из основ протоколов CORBA / RMI;
- изменить способ загрузки байт-кода (например, можно использовать зашифрованный байт-код класса Java);
- модифицировать загруженный байт-код (например, для переплетения аспектов во время загрузки при использовании аспектно-ориентированного программирования);
⭐ Области данных времени выполнения
Run-Time Data Areas. JVM выделяет множество областей данных во время выполнения, которые используются во время выполнения программы. Некоторые участки данных созданы JVM во время старта и уничтожаются во время её выключения. Другие создаются для каждого потока и уничтожаются, когда поток уничтожается.
The pc Register (PCR)
Виртуальная машина Java может поддерживать много потоков исполнения одновременно. Каждый поток виртуальной машины Java имеет свой собственный регистр PC (program counter). В любой момент каждый поток виртуальной машины Java выполняет код одного метода, а именно текущий метод для этого потока. Если этот метод не является native, регистр pc содержит адрес инструкции виртуальной машины Java, выполняемой в настоящее время.
Коротко говоря: для одного потока, существует один PCR, который создается при запуске потока. PCR хранит адрес выполняемой сейчас инструкции JVM.
Java Virtual Machine Stacks
Каждый поток в JVM имеет собственный стек, созданный одновременно с потоком. Стек в JVM хранит frames. Cтеки в JVM могут иметь фиксированный размер или динамически расширяться и сжиматься в соответствии с требованиями вычислений.
Heap
JVM имеет heap (кучу), которая используется всеми потоками виртуальной машины Java. Куча - это область данных времени выполнения, из которой выделяется память для всех экземпляров и массивов классов. Куча создается при запуске виртуальной машины. Хранилище для объектов восстанавливается автоматической системой управления данными (известной как сборщик мусора); объекты никогда не освобождаются явно. JVM не предполагает какого-либо конкретного типа системы автоматического управления хранением данных, и метод управления может быть выбран в соответствии с системными требованиями разработчика. Куча может иметь фиксированный размер или может быть расширена в соответствии с требованиями вычислений и может быть сокращена, если большая куча становится ненужной. Память для кучи не должна быть смежной.
Method Area
JVM имеет область методов, которая является общей для всех потоков. Она хранит структуры для каждого класса, такие как пул констант, данные полей и методов, а также код для методов и конструкторов, включая специальные методы, используемые при инициализации классов и экземпляров, и инициализации интерфейса. Хотя область метода является логически частью кучи, простые реализации могут не обрабатываться сборщиком мусора. Область метода может иметь фиксированный размер или может быть расширена в соответствии с требованиями вычислений и может быть сокращена, если большая область метода становится ненужной.
Run-Time Constant Pool
A run-time constant pool существует для каждого класса или интерфейса в рантайме и представленно constant_pool таблицей в *.class
файле.
Он содержит несколько видов констант: от числовых литералов, известных во время компиляции, до ссылок на методы и поля,
которые должны быть разрешены во время выполнения. Сам run-time constant pool выполняет функцию,
аналогичную функции таблицы символов для обычного языка программирования, хотя он содержит более широкий диапазон данных, чем типичная таблица символов.
Каждый run-time constant pool отделён от JVM's method area. JVM создаёт run-time constant pool вместе с созданием class или interface.
Native Method Stacks
Реализация виртуальной машины Java может использовать обычные стеки, обычно называемые «стеки Си», для поддержки native methods (методов, написанных на языке, отличном от языка программирования Java).
⭐ Frames
Frame используется для хранения данных и частичных результатов, а также для выполнения динамического связывания, возврата значений для методов и отправки исключений. Новый frame создается каждый раз, когда вызывается метод. Frame уничтожается, когда завершается вызов метода, является ли это завершение нормальным или резким (он генерирует неперехваченное исключение). Frames выделяются из стека потока, создающего frame. Каждый frame имеет свой собственный массив локальных переменных, свой собственный стек операндов и ссылку на пул констант во время выполнения класса текущего метода. Размеры массива локальных переменных и стека операндов определяются во время компиляции и предоставляются вместе с кодом для метода, связанного с фреймом. Таким образом, размер структуры данных, frame-а зависит только от реализации виртуальной машины Java, и память для этих структур может быть выделена одновременно при вызове метода.
Только один frame активен в любой точке данного потока управления - метода выполнения, и это frame называется текущим, а его метод известен как текущий метод. Класс, в котором определен текущий метод, является текущим классом. Операции над локальными переменными и стеком операндов обычно выполняются со ссылкой на текущий frame.
Frame перестает быть текущим, если его метод вызывает другой метод или если его метод завершается. Когда метод вызывается, новый frame создается и становится текущим, когда управление переходит к новому методу. При возврате метода текущий frame передает результат вызова метода, если таковой имеется, в предыдущий frame. Текущий frame затем отбрасывается, так как предыдущий frame становится текущим. Обратите внимание, что frame, созданный потоком, является локальным для этого потока и на него не может ссылаться ни один другой поток.
Локальные переменные
Каждый frame содержит массив переменных, известных как его локальные переменные. Длина массива локальных переменных frame определяется во время компиляции и предоставляется в двоичном представлении класса или интерфейса вместе с кодом для метода, связанного с frame-ом. Единичная локальная переменная может хранить значение типа: boolean, byte, char, short, int, float, reference, or returnAddress. Пара локальных переменных может хранить значение типов: long или double.
Локальные переменные адресуются путем индексации. Индекс первой локальной переменной равен нулю.
Значение типа long или типа double занимает две последовательные локальные переменные.
JVM использует локальные переменные для передачи параметров при вызове метода. При вызове метода класса все параметры передаются в последовательных локальных переменных, начиная с локальной переменной 0. При вызове метода экземпляра локальная переменная 0 всегда используется для передачи ссылки на объект, для которого вызывается метод экземпляра (this в Java). Любые параметры впоследствии передаются в последовательных локальных переменных, начиная с локальной переменной 1.
Стеки операндов (Operand Stacks)
Каждый frame содержит стек «последний вошел - первый вышел» (LIFO), известный как стек операндов. Максимальная глубина стека операндов frame-a определяется во время компиляции и предоставляется вместе с кодом для метода, связанного с frame-ом.
Стек операнда пуст при создании frame-a, который его содержит. JVM предоставляет инструкции для загрузки констант или значений из локальных переменных или полей в стек операндов. Другие инструкции JVM берут операнды из стека операндов, оперируют с ними и помещают результат обратно в стек операндов. Стек операндов также используется для подготовки параметров для передачи в методы и для получения результатов метода.
Для примера, инструкция iadd суммирует два int-вых значения. От стека операндов требуется, чтобы два int-вых значения были наверху стека. Значения удаляются из стека, операция pop. Суммируются и их сумма помещается в стек операндов.
Динамическое связывание (Dynamic Linking)
Каждый frame содержит ссылку на run-time constant pool для типа текущего метода для поддержки динамического связывания кода метода. Доступ к вызываемым методам и переменным осуществляется через символические ссылки из class файла. Динамическое связывание преобразует эти символьные ссылки на методы в конкретные ссылки на методы, загружая классы по мере необходимости для разрешения пока еще не определенных символов, и преобразует обращения к переменным в соответствующие смещения в структурах хранения, связанных с расположением этих переменных во время выполнения.
Позднее связывание методов и переменных вносит изменения в другие классы, которые метод использует с меньшей вероятностью нарушить этот код.
Нормальное завершение вызова метода
Вызов метода завершается нормально, если этот вызов не вызывает исключение, либо непосредственно из JVM, либо в результате выполнения явного оператора throw. Если вызов текущего метода завершается нормально, то значение может быть возвращено вызывающему методу. Это происходит, когда вызванный метод выполняет одну из инструкций возврата, выбор которых должен соответствовать типу возвращаемого значения (если оно есть).
Текущий frame используется в этом случае для восстановления состояния инициатора, включая его локальные переменные и стек операндов, с соответствующим образом увеличенным программным счетчиком инициатора, чтобы пропустить инструкцию вызова метода. Затем выполнение обычно продолжается в frame вызывающего метода с возвращенным значением (если оно есть), помещаемым в стек операндов этого frame.
Резкое завершение вызова метода
Вызов метода завершается преждевременно, если при выполнении инструкции JVM в методе выдает исключение, и это исключение не обрабатывается в методе. Выполнение команды throw также приводит к явному выбрасыванию исключения, и, если исключение не перехватывается текущим методом, приводит к неожиданному завершению вызова метода. Вызов метода, который завершается внезапно, никогда не возвращает значение своему вызывающему.
⭐ Execution Engine
Байт-код, назначенный run-time data areas, будет выполнен execution engine. Механизм выполнения считывает байт-код и выполняет его по частям.
Interpreter
Интерпретатор интерпретирует байт-код быстро, но выполняется медленно. Недостаток интерпретатора заключается в том, что, когда один метод вызывается несколько раз, каждый раз требуется новая интерпретация.
JIT Compiler
JIT-компилятор устраняет недостатки интерпретатора. Механизм выполнения будет использовать помощь интерпретатора при преобразовании байт-кода, но когда он находит повторный код, он использует JIT-компилятор, который компилирует весь байт-код и изменяет его на собственный код. Этот нативный код будет использоваться непосредственно для повторных вызовов методов, которые улучшают производительность системы.
- Генератор промежуточного кода (Intermediate Code Generator). Производит промежуточный код.
- Code Optimizer. Отвечает за оптимизацию промежуточного кода, сгенерированного выше.
- Генератор целевого кода (Target Code Generator). Отвечает за генерацию машинного кода или родной код.
- Профилировщик (Profiler). Специальный компонент, отвечающий за поиск горячих точек, то есть, вызывается ли метод несколько раз или нет.
⭐ Что такое Heap и Stack память в Java? Какая разница между ними?
Heap (куча) используется Java Runtime для выделения памяти под объекты и классы. Создание нового объекта также происходит в куче. Это же является областью работы сборщика мусора. Любой объект, созданный в куче, имеет глобальный доступ и на него могут ссылаться из любой части приложения.
Stack (стек) это область хранения данных также находящееся в общей оперативной памяти (RAM). Всякий раз, когда вызывается метод, в памяти стека создается новый блок, который содержит примитивы и ссылки на другие объекты в методе. Как только метод заканчивает работу, блок также перестает использоваться, тем самым предоставляя доступ для следующего метода. Размер стековой памяти намного меньше объема памяти в куче. Стек в Java работает по схеме LIFO (Последний-зашел-Первый-вышел)
Различия между Heap и Stack памятью:
- Куча используется всеми частями приложения, в то время как стек используется только одним потоком исполнения программы.
- Всякий раз, когда создается объект, он всегда хранится в куче, а в памяти стека содержится лишь ссылка на него. Память стека содержит только локальные переменные примитивных типов и ссылки на объекты в куче.
- Объекты в куче доступны с любой точки программы, в то время как стековая память не может быть доступна для других потоков.
- Стековая память существует лишь какое-то время работы программы, а память в куче живет с самого начала до конца работы программы.
- Если память стека полностью занята, то Java Runtime бросает исключение
java.lang.StackOverflowError
. Если заполнена память кучи, то бросается исключениеjava.lang.OutOfMemoryError: Java Heap Space
. - Размер памяти стека намного меньше памяти в куче.
- Из-за простоты распределения памяти, стековая память работает намного быстрее кучи.
Для определения начального и максимального размера памяти в куче используются -Xms
и -Xmx
опции JVM. Для стека определить размер памяти можно с помощью опции -Xss
.
⭐ Garbage Collector
Механизм сборки мусора - это процесс освобождения места в куче, для возможности добавления новых объектов.
Объекты создаются посредством оператора new
, тем самым присваивая объекту ссылку. Для окончания работы с объектом достаточно просто перестать на него ссылаться, например, присвоив переменной ссылку на другой объект или значение null
; прекратить выполнение метода, чтобы его локальные переменные завершили свое существование естественным образом. Объекты, ссылки на которые отсутствуют, принято называть мусором (garbage), который будет удален.
Виртуальная машина Java, применяя механизм сборки мусора, гарантирует, что любой объект, обладающий ссылками, остается в памяти — все объекты, которые недостижимы из исполняемого кода, ввиду отсутствия ссылок на них, удаляются с высвобождением отведенной для них памяти. Точнее говоря, объект не попадает в сферу действия процесса сборки мусора, если он достижим посредством цепочки ссылок, начиная с корневой (GC Root) ссылки, т.е. ссылки, непосредственно существующей в выполняемом коде.
Память освобождается сборщиком мусора по его собственному «усмотрению». Программа может успешно завершить работу, не исчерпав ресурсов свободной памяти или даже не приблизившись к этой черте и поэтому ей так и не потребуются «услуги» сборщика мусора.
Мусор собирается системой автоматически, без вмешательства пользователя или программиста, но это не значит, что этот процесс не требует внимания вовсе. Необходимость создания и удаления большого количества объектов существенным образом сказывается на производительности приложений и, если быстродействие программы является важным фактором, следует тщательно обдумывать решения, связанные с созданием объектов, — это, в свою очередь, уменьшит и объем мусора, подлежащего утилизации.
⭐ Для чего нужен сборщик мусора?
Сборщик мусора (Garbage Collector) должен делать всего две вещи:
- Находить мусор - неиспользуемые объекты. (Объект считается неиспользуемым, если ни одна из сущностей в коде, выполняемом в данный момент, не содержит ссылок на него, либо цепочка ссылок, которая могла бы связать объект с некоторой сущностью приложения, обрывается);
- Освобождать память от мусора.
Существует два подхода к обнаружению мусора:
- Reference counting;
- Tracing
Reference counting (подсчёт ссылок). Суть этого подхода состоит в том, что каждый объект имеет счетчик. Счетчик хранит информацию о том, сколько ссылок указывает на объект. Когда ссылка уничтожается, счетчик уменьшается. Если значение счетчика равно нулю, - объект можно считать мусором. Главным минусом такого подхода является сложность обеспечения точности счетчика. Также при таком подходе сложно выявлять циклические зависимости (когда два объекта указывают друг на друга, но ни один живой объект на них не ссылается), что приводит к утечкам памяти.
Главная идея подхода Tracing (трассировка) состоит в утверждении, что живыми могут считаться только те объекты, до которых мы можем добраться из корневых точек (GC Root) и те объекты, которые доступны с живого объекта. Всё остальное - мусор.
Существует 4 типа корневых точки:
- Локальные переменные и параметры методов;
- Потоки;
- Статические переменные;
- Ссылки из JNI.
Самое простое java приложение будет иметь корневые точки:
- Локальные переменные внутри
main()
метода и параметрыmain()
метода; - Поток который выполняет
main()
; - Статические переменные класса, внутри которого находится
main()
метод.
Таким образом, если мы представим все объекты и ссылки между ними как дерево, то нам нужно будет пройти с корневых узлов (точек) по всем рёбрам. При этом узлы, до которых мы сможем добраться - не мусор, все остальные - мусор. При таком подходе циклические зависимости легко выявляются. HotSpot VM использует именно такой подход.
Для очистки памяти от мусора существуют два основных метода:
- Copying collectors
- Mark-and-sweep
При copying collectors подходе память делится на две части «from-space» и «to-space», при этом сам принцип работы такой:
- Объекты создаются в «from-space»;
- Когда «from-space» заполняется, приложение приостанавливается;
- Запускается сборщик мусора. Находятся живые объекты в «from-space» и копируются в «to-space»;
- Когда все объекты скопированы «from-space» полностью очищается;
- «to-space» и «from-space» меняются местами.
Главный плюс такого подхода в том, что объекты плотно забивают память. Минусы подхода:
- Приложение должно быть остановлено на время, необходимое для полного прохождения цикла сборки мусора;
- В худшем случае (когда все объекты живые) «form-space» и «to-space» будут обязаны быть одинакового размера.
Алгоритм работы mark-and-sweep можно описать так:
- Объекты создаются в памяти;
- В момент, когда нужно запустить сборщик мусора приложение приостанавливается;
- Сборщик проходится по дереву объектов, помечая живые объекты;
- Сборщик проходится по всей памяти, находя все не отмеченные куски памяти и сохраняя их в «free list»;
- Когда новые объекты начинают создаваться они создаются в памяти доступной во «free list».
Минусы этого способа:
- Приложение не работает пока происходит сборка мусора;
- Время остановки напрямую зависит от размеров памяти и количества объектов;
- Если не использовать «compacting» память будет использоваться не эффективно.
Сборщики мусора HotSpot VM используют комбинированный подход Generational Garbage Collection, который позволяет использовать разные алгоритмы для разных этапов сборки мусора. Этот подход опирается на том, что:
- большинство создаваемых объектов быстро становятся мусором;
- существует мало связей между объектами, которые были созданы в прошлом и только что созданными объектами.
Разновидности GC
Java HotSpot VM предоставляет разработчикам на выбор четыре различных сборщика мусора:
- Serial (последовательный) — самый простой вариант для приложений с небольшим объемом данных и не требовательных к задержкам. На данный момент используется сравнительно редко, но на слабых компьютерах может быть выбран виртуальной машиной в качестве сборщика по умолчанию. Использование Serial GC включается опцией
-XX:+UseSerialGC
. - Parallel (параллельный) — наследует подходы к сборке от последовательного сборщика, но добавляет параллелизм в некоторые операции, а также возможности по автоматической подстройке под требуемые параметры производительности. Параллельный сборщик включается опцией
-XX:+UseParallelGC
. - Concurrent Mark Sweep (CMS) — нацелен на снижение максимальных задержек путем выполнения части работ по сборке мусора параллельно с основными потоками приложения. Подходит для работы с относительно большими объемами данных в памяти. Использование CMS GC включается опцией
-XX:+UseConcMarkSweepGC
. - Garbage-First (G1) — создан для замены CMS, особенно в серверных приложениях, работающих на многопроцессорных серверах и оперирующих большими объемами данных. G1 включается опцией Java
-XX:+UseG1GC
.
⭐ Последовательный GC. Поколения
Serial Garbage Collector (Последовательный сборщик мусора) был одним из первых сборщиков мусора в HotSpot VM. Во время работы этого сборщика приложения приостанавливается и продолжает работать только после прекращения сборки мусора.
Память приложения делится на три пространства:
- Young generation. Объекты создаются именно в этом участке памяти.
- Old generation. В этот участок памяти перемещаются объекты, которые переживают «minor garbage collection».
- Permanent generation. Тут хранятся метаданные об объектах, Class data sharing (CDS), пул строк (String pool). Permanent область делится на две: только для чтения и для чтения-записи. Очевидно, что в этом случае область только для чтения не чистится сборщиком мусора никогда.
Область памяти Young generation состоит из трёх областей: Eden и двух меньших по размеру Survivor spaces - To space и From space. Большинство объектов создаются в области Eden, за исключением очень больших объектов, которые не могут быть размещены в ней и поэтому сразу размещаются в Old generation. В Survivor spaces перемещаются объекты, которые пережили по крайней мере одну сборку мусора, но ещё не достигли порога «старости» (tenuring threshold), чтобы быть перемещенными в Old generation.
Когда Young generation заполняется, то в этой области запускается процесс лёгкой сборки (minor collection), в отличие от процесса сборки, проводимого над всей кучей (full collection). Он происходит следующим образом: в начале работы одно из Survivor spaces - To space, является пустым, а другое - From space, содержит объекты, пережившие предыдущие сборки. Сборщик мусора ищет живые объекты в Eden и копирует их в To space, а затем копирует туда же и живые «молодые» (то есть не пережившие еще заданное число сборок мусора) объекты из From space. Старые объекты из From space перемещаются в Old generation. После лёгкой сборки From space и To space меняются ролями, область Eden становится пустой, а число объектов в Old generation увеличивается.
Если в процессе копирования живых объектов To space переполняется, то оставшиеся живые объекты из Eden и From space, которым не хватило места в To space, будут перемещены в Old generation, независимо от того, сколько сборок мусора они пережили.
Поскольку при использовании этого алгоритма сборщик мусора просто копирует все живые объекты из одной области памяти в другую, то такой сборщик мусора называется copying (копирующий). Очевидно, что для работы копирующего сборщика мусора у приложения всегда должна быть свободная область памяти, в которую будут копироваться живые объекты, и такой алгоритм может применяться для областей памяти сравнительно небольших по отношению к общему размеру памяти приложения. Young generation как раз удовлетворяет этому условию (по умолчанию на машинах клиентского типа эта область занимает около 10% кучи (значение может варьироваться в зависимости от платформы)).
Однако, для сборки мусора в Old generation, занимающем большую часть всей памяти, используется другой алгоритм.
В Old generation сборка мусора происходит с использованием алгоритма mark-sweep-compact, который состоит из трёх фаз. В фазе Mark (пометка) сборщик мусора помечает все живые объекты, затем, в фазе Sweep (очистка) все не помеченные объекты удаляются, а в фазе Сompact (уплотнение) все живые объекты перемещаются в начало Old generation, в результате чего свободная память после очистки представляет собой непрерывную область. Фаза уплотнения выполняется для того, чтобы избежать фрагментации и упростить процесс выделения памяти в Old generation.
Когда свободная память представляет собой непрерывную область, то для выделения памяти под создаваемый объект можно использовать очень быстрый (около десятка машинных инструкций) алгоритм bump-the-pointer: адрес начала свободной памяти хранится в специальном указателе, и когда поступает запрос на создание нового объекта, код проверяет, что для нового объекта достаточно места, и, если это так, то просто увеличивает указатель на размер объекта.
Последовательный сборщик мусора отлично подходит для большинства приложений, использующих до 200 мегабайт кучи, работающих на машинах клиентского типа и не предъявляющих жёстких требований к величине пауз, затрачиваемых на сборку мусора. В то же время модель «stop-the-world» может вызвать длительные паузы в работе приложения при использовании больших объёмов памяти. Кроме того, последовательный алгоритм работы не позволяет оптимально использовать вычислительные ресурсы компьютера, и последовательный сборщик мусора может стать узким местом при работе приложения на многопроцессорных машинах.