предисловие
Многопоточное программирование сложно, или написать код, который хорошо работает в многопоточных условиях, сложно. Java предоставляет синхронизированные и изменчивые ключевые слова, а также класс Lock и классы, связанные с Atomic, чтобы помочь нам правильно реализовать логику параллелизма, но я по-прежнему стараюсь максимально избегать параллелизма в реальной работе, и существует ленивый подход, который требует параллельного доступа. Переменные всегда украшаются volatile.
Недавно я столкнулся с двумя примерами, связанными с параллелизмом: один был ошибкой в логике без блокировок, реализованной классом AtomicInteger, написанным коллегой, граничные условия не обрабатывались должным образом, что приводило к бесконечному циклу, другой — сервисом. к относительно высокой нагрузке в обычное время существует проблема, заключающаяся в том, что отмена смещенной блокировки занимает слишком много времени.
Аннулирование блокировки смещения
Предвзятые блокировки на самом деле являются оптимизацией блокировок с помощью JVM, которая предполагает, что только один поток фактически пытается получить доступ к блокировке. Реализация смещенной блокировки очень проста. Когда поток обращается к замку, держатель блокировки непосредственно помечается как поток. Когда поток снова пытается получить блокировку, ему нужно только проверить метку держателя. Оптимизация предвзятой блокировки может эффективно улучшить производительность программы в сценарии, где нет многопоточной конкуренции, но когда предположение об «отсутствии многопоточной конкуренции» не выполняется, для предвзятой блокировки требуется дополнительная логика. отозван, и этот отзыв может привести к длительной паузе, влияющей на производительность программы.
Причина, по которой отмена предвзятой блокировки может вызвать длительные паузы, заключается в том, что отзыв предвзятой блокировки на самом деле должен быть выполнен в безопасной точке. Аннулирование предвзятой блокировки требует приостановки потока, которому принадлежит блокировка, и управления его стеком, поэтому это нужно делать в безопасной точке. Время, необходимое программе для входа в точку безопасности, неизвестно, и конкретная причина связана с конкретной реализацией точки безопасности.
safepoint
Точка безопасности (далее — точка безопасности) — важное понятие в JVM, оно будет встречаться во многих сценариях в JVM, наиболее распространенным из них должен быть GC (хотя я упоминал ранее о предвзятом отзыве блокировки). Смысл точки сохранения представляет собой определенные фиксированные позиции в программе, где состояние программы «определяется».В это время jvm может выполнять некоторые специальные операции в соответствии с состоянием программы, такие как:
- gc: gc нужно очистить объекты, которые больше не живы, поэтому необходимо «наверняка» знать, какие объекты больше не живы. Сборщик мусора также должен сканировать стек каждого потока, поэтому ему необходимо «определить» тип (ссылку или значение) каждого объекта в стеке.
- Аннулирование предвзятой блокировки: это было упомянуто ранее, потому что необходимо приостановить поток и одновременно работать со стеком потока.
- Обновите OopMap: oop (обычный указатель объекта) — это структура данных, используемая в виртуальной машине точки доступа для записи метаданных объекта.Он записывает тип данных, соответствующий каждому смещению в объекте. OopMap в основном используется для реализации точного GC. Чтобы узнать о точном GC, см.здесь
- Деоптимизация кода/Очистка кеша кода/Переопределение класса/Различные операции отладки: отздесь
расположение сейфпойнта
В управлении временем выполнения JVM точка безопасности используется для приостановки всей программы (Stop the World), а затем для выполнения некоторых специальных операций. Идея safepoint тоже очень проста: когда нам нужно выполнить какие-то специальные операции (типа gc), мы устанавливаем какие-то точки паузы в определенных частях программы, и когда поток доходит до этих точек паузы, он приостанавливается сам. После того, как все потоки будут приостановлены, мы выполним исходную спецоперацию. Конкретные местоположения точек безопасности обычно включают следующее:
- После каждой команды байт-кода (интерпретируемый режим)
- Все методы возвращаются раньше (режим JIT)
- Конец всех циклов без подсчета (режим JIT)
Установка точки сохранения только в конце несчетного цикла представляет проблему: если в программе очень большой счетный цикл (например, циклы 100w), это может привести к тому, что точка безопасности надолго зависнет в работе всей программы, т.к. остальные уже висят.Всем потокам нужно дождаться окончания выполнения этого большого цикла.
Как упоминалось выше, установка точки сохранения приостановит все потоки, поэтому кто будет выполнять конкретный отзыв gc или смещения блокировки? В выводе jstack видно, что есть поток под названием «VMThread», который отвечает за обработку различных специальных операций во время STW.
safe region
Safepoint позволяет активно приостанавливать запущенные потоки, в то время как потоки в других состояниях (например, спящие или блокирующиеся при блокировке) не могут активно запускаться в safepoint. Для этого случая в jvm задается понятие безопасного региона, когда поток находится в определенном состоянии, jvm думает, что в этом случае поток не будет вносить никаких изменений в кучу jvm, поэтому он не уничтожит состояние jvm «ОК», поэтому эти потоки можно считать безопасными (в безопасной зоне). Когда поток в безопасной области хочет выйти из безопасной области, также необходимо проверить, должен ли он быть активно приостановлен. Потоки, которые устанавливают следующие состояния в jvm, находятся в безопасной области:
- заблокирован или ждет
- Метод JNI выполняется
Поэтому, когда потоки находятся в вышеуказанных состояниях, мы думаем, что они могут выполнять специальные операции, такие как достижение точки сохранения. Чтобы предотвратить внесение изменений в кучу jvm после возвращения из безопасного региона, поток будет активно приостанавливаться, когда он возвращается из безопасного региона в STW.
Реализация подвеса нити
Установив точку сохранения в определенном месте, мы можем заставить программу зависать, когда это необходимо. Когда STW требуется, точка безопасности будет активирована. Когда каждый поток запускается в точку сохранения, он будет активно проверять состояние точки сохранения. Если точка сохранения активирована, поток переходит в состояние приостановки. Проще говоря,Потоки будут активно обращаться к определенной странице памяти при проверке точки сохранения, а когда для STW установлено значение «нечитаемый», эта страница памяти становится нечитаемой, поэтому каждый поток, пытающийся прочитать эту страницу памяти, также будет зависать.. Этот процесс проверки активно вставляется в инструкцию JIT-компилятором.
В частности, когда поток находится в разных состояниях, способ его приостановки также отличается:
- Когда поток находится в интерпретируемом выполнении, интерпретатор заставит следующую инструкцию указать на инструкцию, которая проверяет точку безопасности.
- Когда поток выполняет метод JNI, VMThread не ждет его возврата, а считает, что он находится в безопасном регионе. Когда он возвращается, он блокируется до тех пор, пока STW не закончится
- Когда поток выполняет скомпилированный метод, скомпилированный код будет нести логику проверки точки сохранения, мы можем сделать конкретную страницу памяти нечитаемой.
- Когда поток блокируется, VMThread не ждет его возврата, а считает, что он находится в безопасном регионе. Когда он возвращается, он блокируется до тех пор, пока STW не закончится
Когда поток пытается получить доступ к странице памяти, помеченной как нечитаемая, срабатывает сигнал SIGSEGV, тем самым запуская обработчик сигнала в jvm.Когда обработчик сигнала получает сигнал SIGSEGV, он подтверждает, срабатывает ли сигнал проверкой точки сохранения , и если это так, он приостановит себя.
разное
Много ссылокблог iter_zc, настоящим благодарю вас.