Недавно у нас была ситуация, когда одно из наших JVM-продуктов произвольно замерзало. Процесс Java сжигал CPU, но вся видимая активность прекращалась: никакого выхода журнала, ничего не записывалось в журнал GC, никакого ответа на какой-либо сетевой запрос и т.д. Процесс будет сохраняться в этом состоянии до перезапуска.
Оказалось, что класс org.mozilla.javascript.DToA при вызове на определенных входах запутается и вызовет BigInteger.pow с огромными значениями (например, 5 ^ 2147483647), что вызывает зависание JVM. Я предполагаю, что некоторый большой цикл, возможно, в java.math.BigInteger.multiplyToLen, был JIT'ed без проверки безопасности в цикле. В следующий раз, когда JVM необходимо приостановить сборку мусора, она замерзнет, потому что поток, выполняющий код BigInteger, не достигнет безопасного места в течение очень долгого времени.
Мой вопрос: в будущем, как я могу диагностировать проблему с безопасностью, подобную этой? kill -3 не производил никакого выхода; Я полагаю, он полагается на safepoints для создания точных стеков. Есть ли какой-либо производственный безопасный инструмент, который может извлекать стеки из работающей JVM без ожидания safepoint? (В этом случае мне повезло и удалось захватить набор трассировок стека сразу после вызова BigInteger.pow, но до того, как он проделал свой путь до достаточно большого входа, чтобы полностью вклинить JVM. Без этого удачи, я "Не знаю, как бы мы когда-либо диагностировали проблему.)
Изменить: следующий код иллюстрирует проблему.
// Spawn a background thread to compute an enormous number.
new Thread(){ @Override public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
BigInteger.valueOf(5).pow(100000000);
}}.start();
// Loop, allocating memory and periodically logging progress, so illustrate GC pause times.
byte[] b;
for (int outer = 0; ; outer++) {
long startMs = System.currentTimeMillis();
for (int inner = 0; inner < 100000; inner++) {
b = new byte[1000];
}
System.out.println("Iteration " + outer + " took " + (System.currentTimeMillis() - startMs) + " ms");
}
Это запускает фоновый поток, который ждет 5 секунд, а затем запускает огромное вычисление BigInteger. На переднем плане он затем многократно выделяет серию из 100 000 блоков 1K, регистрируя прошедшее время для каждой серии 100 МБ. В течение 5 секунд каждая серия из 100 Мбайт работает примерно на 20 миллисекунд на моем MacBook Pro. Как только начнутся вычисления BigInteger, мы начнем чередоваться с длинными паузами. В одном тесте паузы были последовательно 175 мс, 997 мс, 2927 мс, 4222 мс и 22617 мс (после чего я прервал тест). Это согласуется с BigInteger.pow(), вызывающим серию все более крупных операций умножения, каждый из которых занимает больше времени, чтобы достичь безопасного места.