Следующий фрагмент кода выполняет два потока, один - это простой таймер, регистрирующий каждую секунду, второй - бесконечный цикл, который выполняет остаточную операцию:
public class TestBlockingThread {
private static final Logger LOGGER = LoggerFactory.getLogger(TestBlockingThread.class);
public static final void main(String[] args) throws InterruptedException {
Runnable task = () -> {
int i = 0;
while (true) {
i++;
if (i != 0) {
boolean b = 1 % i == 0;
}
}
};
new Thread(new LogTimer()).start();
Thread.sleep(2000);
new Thread(task).start();
}
public static class LogTimer implements Runnable {
@Override
public void run() {
while (true) {
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// do nothing
}
LOGGER.info("timeElapsed={}", System.currentTimeMillis() - start);
}
}
}
}
Это дает следующий результат:
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=13331
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1006
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
Я не понимаю, почему бесконечная задача блокирует все остальные потоки на 13,3 секунды. Я пытался изменить приоритеты потоков и другие настройки, ничего не помогло.
Если у вас есть предложения по исправлению этой ситуации (включая настройку параметров переключения контекста ОС), сообщите мне.
java
multithreading
kms333
источник
источник
-XX:+PrintCompilation
расширенной задержкой я получаю следующее: TestBlockingThread :: lambda $ 0 @ 2 (24 байта) COMPILE SKIPPED: тривиальный бесконечный цикл (повторная попытка на другом уровне)-Djava.compiler=NONE
, этого не произойдет.Ответы:
После всех приведенных здесь объяснений (благодаря Питеру Лоури ) мы обнаружили, что основным источником этой паузы является то, что точка безопасности внутри цикла достигается довольно редко, поэтому требуется много времени, чтобы остановить все потоки для замены кода, скомпилированного JIT.
Но я решил пойти глубже и выяснить, почему точка сохранения достигается редко. Меня немного сбивает с толку, почему обратный переход
while
цикла небезопасен в этом случае.Поэтому я призываю
-XX:+PrintAssembly
во всей красе на помощьПосле некоторого расследования я обнаружил, что после третьей перекомпиляции лямбда-
C2
компилятора опросы безопасной точки внутри цикла были полностью удалены.ОБНОВИТЬ
На этапе профилирования переменная
i
никогда не была равна 0. Вот почему мыC2
спекулятивно оптимизировали эту ветвь, чтобы цикл был преобразован во что-то вродеОбратите внимание, что изначально бесконечный цикл был преобразован в обычный конечный цикл со счетчиком! Из-за оптимизации JIT для исключения опросов безопасных точек в циклах с конечным числом подсчетов, в этом цикле также не было опросов безопасных точек.
Спустя какое-то время
i
завернули обратно0
, и необычная ловушка была взята. Метод был деоптимизирован и продолжил выполнение в интерпретаторе. Во время перекомпиляции с новыми знаниямиC2
распознал бесконечный цикл и отказался от компиляции. Остальная часть метода продолжилась в интерпретаторе с соответствующими точками сохранения.Существует большое должен прочитать сообщение в блоге «Safepoints: Значение, побочные эффекты и Накладные» по Ницан Wakart покрывающих safepoints и этот конкретный вопрос.
Известно, что устранение безопасной точки в очень длинных счетных циклах является проблемой. Ошибка
JDK-5014723
(спасибо Владимиру Иванову ) решает эту проблему.Обходной путь доступен до тех пор, пока ошибка не будет окончательно исправлена.
-XX:+UseCountedLoopSafepoints
(это приведет к снижению общей производительности и может привести к сбою JVMJDK-8161147
). После его использованияC2
компилятор продолжает сохранять точки сохранения на обратных прыжках, и исходная пауза полностью исчезает.Вы можете явно отключить компиляцию проблемного метода, используя
-XX:CompileCommand='exclude,binary/class/Name,methodName'
Или вы можете переписать свой код, добавив точку сохранения вручную. Например,
Thread.yield()
вызов в конце цикла или даже изменениеint i
наlong i
(спасибо, Nitsan Wakart ) также исправит паузу.источник
-XX:+UseCountedLoopSafepoints
в продакшене, так как это может привести к сбою JVM . На данный момент лучший обходной путь - вручную разделить длинный цикл на более короткие.c2
убирает точки сохранения! но я еще не понял, что будет дальше. насколько я понимаю, после разворачивания цикла не осталось никаких безопасных точек (?), и похоже, что нет способа сделать stw. так происходит какой-то таймаут и происходит деоптимизация?i
никогда не бывает 0, поэтому цикл теоретически преобразуется во что-то вродеfor (int i = osr_value; i != 0; i++) { if (1 % i == 0) uncommon_trap(); } uncommon_trap();
обычного цикла с конечным счетом. Послеi
возврата к 0 принимается необычная ловушка, метод деоптимизируется и выполняется в интерпретаторе. Во время перекомпиляции с новыми знаниями JIT распознает бесконечный цикл и прекращает компиляцию. Остальная часть метода выполняется в интерпретаторе с соответствующими точками сохранения.Короче говоря, у вашего цикла нет безопасной точки внутри, кроме тех случаев, когда
i == 0
она достигается. Когда этот метод компилируется и запускает замену кода, ему необходимо перевести все потоки в безопасную точку, но это занимает очень много времени, блокируя не только поток, выполняющий код, но и все потоки в JVM.Я добавил следующие параметры командной строки.
Я также изменил код, чтобы использовать плавающую точку, которая, похоже, занимает больше времени.
На выходе я вижу
Примечание: для замены кода потоки должны быть остановлены в безопасной точке. Однако здесь оказывается, что такая безопасная точка достигается очень редко (возможно, только при
i == 0
изменении задачи наЯ вижу аналогичную задержку.
Если аккуратно добавить код в цикл, вы получите большую задержку.
получает
Однако измените код, чтобы использовать собственный метод, который всегда имеет безопасную точку (если это не внутренний метод).
печать
Примечание: добавление
if (Thread.currentThread().isInterrupted()) { ... }
в цикл добавляет безопасную точку.Примечание. Это произошло на 16-ядерной машине, поэтому ресурсов ЦП нет.
источник
Нашел ответ почему . Они называются безопасными точками и наиболее известны как Stop-The-World, возникающие из-за GC.
См. Эти статьи: Ведение журнала остановок в JVM
Читая Глоссарий терминов HotSpot , он определяет следующее:
Работая с вышеупомянутыми флагами, я получаю следующий результат:
Обратите внимание на третье событие STW:
Общее время остановки: 10,7951187 секунд.
Остановка потоков заняла: 10,7950774 секунды.
Сам JIT практически не занимал времени, но как только JVM решила выполнить JIT-компиляцию, она перешла в режим STW, однако, поскольку код, который должен быть скомпилирован (бесконечный цикл), не имеет места вызова , точка безопасности не была достигнута.
STW завершается, когда JIT в конце концов прекращает ожидание и завершает работу кода в бесконечном цикле.
источник
После того, как я проследил за комментариями и самостоятельно провел некоторое тестирование, я считаю, что пауза вызвана JIT-компилятором. Почему JIT-компилятор занимает так много времени, я не могу отладить.
Однако, поскольку вы спросили только, как этого избежать, у меня есть решение:
Превратите ваш бесконечный цикл в метод, где его можно исключить из JIT-компилятора
Запустите вашу программу с этим аргументом виртуальной машины:
-XX: CompileCommand = exclude, PACKAGE.TestBlockingThread :: infLoop (замените PACKAGE информацией о вашем пакете)
Вы должны получить подобное сообщение, чтобы указать, когда метод был бы JIT-скомпилирован:
### Исключая компиляцию: статическая блокировка.TestBlockingThread :: infLoop
вы можете заметить, что я помещаю класс в пакет, называемый блокировкой
источник
i == 0
while
цикле не является безопасной точкой?if (i != 0) { ... } else { safepoint(); }
но это очень редко. то есть. если вы выйдете / прервете цикл, вы получите примерно те же тайминги.