Взаимодействие VM с приложением
Для выполнения ряда внутренних операций виртуальной машине нужно
получать детальную информацию о текущем состоянии исполняемой программы
и иметь возможность безопасно это состояние модифицировать. Например,
для успешной работы сборщика мусора необходимо получать информацию о
гарантированно живых объектах (root set)—объектах,
ссылки на которые хранятся в локальных переменных методов или в
статических полях классов. Если во время сборки какие-либо объекты были
перемещены, все ссылки на них должны быть изменены и указывать на новое
местоположение.
Задача осложняется еще и тем, что в рамках виртуальной машины Java-код может быть как интерпретирован, так и быть заменен на эквивалентную компилированную версию.
Также, процесс деоптимизации компилированного кода (method deoptimization) и откат привязанной блокировки (biased lock revocation, [3])
может производиться только тогда, когда все необходимые потоки
остановлены и есть полная информация об их текущем состоянии (в
основном, расположение текущих значений локальных переменных в
регистрах и на стеке).
Для решения этой задачи виртуальная машина регулярно переходит в безопасное состояние (safepoint), в котором Java-потоки не имеют возможность менять свой стек и взаимодействовать с кучей (heap). Под безопасным состоянием понимается остановка работы
исполняемой программы в гарантированно целостном состоянии.
В Hotspot VM существует специальный системный поток (VMThread), которому через специальную очередь передаются сообщения на выполнение ряда типовых системных операций. При получении очередного запроса, VMThread проверяет, требуется ли гарантировать целостное состояние каких-либо внутренних структур и, если требуется, инициирует процесс перехода в безопасное состояние.
Для каждого Java-потока виртуальная машина может определить в каком
состоянии он находится. В зависимости от текущего состояния потока
меняется и поведение, связанное с блокировкой потока или проверкой
на необходимость оной. Различаются следующие состояния в которых может
находиться Java-поток:
- Режим интерпретатора
- Проверка происходит после исполнения каждого байткода.
- Исполнение компилированного кода
- JIT-компилятор с некоторой регулярностью вставляет в генерируемый код проверки на запрос
о блокировке потока (итерации в цикле (
loop back branches), выход из метода) и информацию о текущем расположении локальных переменных. - Исполнение native code через JNI-вызов
- Виртуальная машина при переходе в безопасное состояние не дожидается блокировки потоков исполняющих native code. В свою очередь, при возвращении из JNI-вызова, обязательно проверяется находится ли виртуальная машина в безопасном состоянии и, если да, то поток останавливается.
- Поток блокирован
- Если во время начала процесса перехода в безопасное состояние поток был блокирован, то он сможет выйти из блокировки только после завершения операции, вызвавшей переход.
- Происходит изменение текущего состояния потока
- Т.к. операция перехода между различными состояниями потока достаточно быстра, чтобы избежать излишних переключений контекста (
context switching), VMThread ждет завершения перехода и дальнейшего блокирования потока.
Остановку Java-потоков исполняющих компилированный код стоит рассмотреть более подробно. Она производится кооперативно. Это обусловлено тем, что некооперативная остановка потоков средствами ОС ненадежна. В случае же если поток останавливается в произвольном месте, извлечение всей необходимой информации о его состоянии слишком сложно и трудоемко.
Принцип проверки на необходимость блокировки прост. В памяти выделяется глобальная страница (guard page), к которой с некоторой периодичностью обращаются все Java-потоки (safepoint polling), пытаясь осуществить запись. Для того чтобы инициировать процесс блокировки, VMThread помечает guard page как
страницу только для чтения и ждет когда все Java-потоки перейдут в
подходящее для продолжения работы состояния. Неудачное обращение
к guard page из компилированного кода сигнализирует о том, что в данный
момент происходит переход в безопасное состояние и обращающийся поток
будет блокирован.
Также VMThread отдельно устанавливает глобальную блокировку. Снятие этой блокировки свидетельствует о завершении операции, требовавшей перехода в безопасное состояние, и все остановленные потоки будут ждать её снятия для продолжения своей работы.
С точки зрения производительности приложения, выбранный метод кооперативной остановки потоков оказывает на неё минимальное влияние. Успешное обращение к guard page крайне дешево. В то же время запросы на блокировку потоков относительно редки.
Что же касается цифр, то наибольшее влияние на производительность оказывается в коде, сгенерированном C1 (client JIT-compiler),
но по оценкам падение производительности составляет менее 0.5%.
Серверный же компилятор за счет ряда агрессивных оптимизаций позволяет
избавиться от большинства проверок. Например, активный инлайнинг
значительно снижает общее число вызовов методов и,
соответственно, выходов из них. Также для циклов некоторых
видов существует возможность во время компиляции точно определить число
итераций и если оно достаточно невелико, проверки в тело цикла можно не
вставлять.
Одной из особенностей текущей реализации является то, что при каждом переходе в безопасное состояние происходит остановка всех потоков, хотя для некоторых операций в этом и нет необходимости. Недостатки данного решения очевидны—потеря доли производительности на многопроцессорных системах. Так что переход к более мелкой гранулярности блокирования потоков где это только допустимо можно рассматривать как одну из вероятных оптимизаций в обозримом будущем.
Ссылки
- GC Points in a Threaded Environment
- The Hotspot Java Virtual Machine
- Новые оптимизации компилятора VM. Часть 1: Синхронизация
Владимир Иванов
опубликовал vmrobot ( май 20 2007, 09:53:53 PM MSD ) Permalink Комментарии [0]
