Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов во время выполнения в JVM. У меня есть ответ на этот вопрос, и у меня также есть много полезных ответов и комментариев, касающихся того, как Java обрабатывает ситуацию, когда требуется большой стек времени выполнения. Я распространил свой вопрос с резюме ответов.
Изначально я хотел увеличить размер стека JVM, чтобы программы запускались без StackOverflowError
.
public class TT {
public static long fact(int n) {
return n < 2 ? 1 : n * fact(n - 1);
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
Соответствующим параметром конфигурации является флаг командной строки java -Xss...
с достаточно большим значением. Для программы TT
выше, она работает следующим образом с OpenJDK JVM:
$ javac TT.java
$ java -Xss4m TT
В одном из ответов также указано, что флаги -X...
зависят от реализации. Я использовал
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1~8.04.3)
OpenJDK 64-Bit Server VM (build 16.0-b13, mixed mode)
Также можно указать большой стек только для одного потока (см. один из ответов, как). Это рекомендуется для java -Xss...
, чтобы избежать потери памяти для потоков, которые не нуждаются в ней.
Мне было любопытно, насколько большой стек имеет нужную программу, поэтому я запустил ее n
увеличено:
- -Xss4m может быть достаточно для
fact(1 << 15)
- -Xss5m может быть достаточно для
fact(1 << 17)
- -Xss7m может быть достаточно для
fact(1 << 18)
- -Xss9m может быть достаточно для
fact(1 << 19)
- -Xss18m может быть достаточно для
fact(1 << 20)
- -Xss35m может быть достаточно для
fact(1 << 21)
- -Xss68m может быть достаточно для
fact(1 << 22)
- -Xss129m может быть достаточно для
fact(1 << 23)
- -Xss258m может быть достаточно для
fact(1 << 24)
- -Xss515m может быть достаточно для
fact(1 << 25)
Из приведенных выше чисел кажется, что Java использует около 16 байт на стек кадров для функции выше, что разумно.
Недостаточно перечисленного перечисления может быть достаточно, потому что требование стека не является детерминированным: он выполняется несколько раз с одним и тем же исходным файлом, а тот же -Xss...
иногда преуспевает, а иногда и дает StackOverflowError
. Например. дл 1 < 20, -Xss18m
было достаточно в 7 пробегах из 10, и -Xss19m
также не всегда было достаточно, но -Xss20m
было достаточно (всего 100 закончилось из 100). Собирает ли сбор мусора, JIT, или что-то еще, вызывает это недетерминированное поведение?
Трассировка стека, напечатанная на StackOverflowError
(и, возможно, с другими исключениями), показывает только самые последние 1024 элемента стека времени выполнения. Ответ ниже показывает, как подсчитать достигнутую точную глубину (которая может быть намного больше 1024).
Многие люди, которые ответили, отметили, что хорошая и безопасная практика кодирования рассматривает альтернативные, менее утомительные реализации одного и того же алгоритма. В общем случае можно преобразовать в набор рекурсивных функций к итеративным функциям (используя, например, объект Stack
, который заполняется в куче, а не в стеке времени выполнения). Для этой конкретной функции fact
ее легко преобразовать. Моя итеративная версия будет выглядеть так:
public class TTIterative {
public static long fact(int n) {
if (n < 2) return 1;
if (n > 65) return 0; // Enough powers of 2 in the product to make it (long)0.
long f = 2;
for (int i = 3; i <= n; ++i) {
f *= i;
}
return f;
}
public static void main(String[] args) {
System.out.println(fact(1 << 15));
}
}
FYI, как показывает приведенное выше итерационное решение, функция fact
не может вычислить точный факториал чисел выше 65 (фактически, даже выше 20), потому что встроенный тип long
Java будет переполняться. Рефакторинг fact
, чтобы он возвращал BigInteger
вместо long
, также дал бы точные результаты для больших входов.