Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов времени выполнения в 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 или что-то еще?
Трассировка стека, напечатанная в a 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));
}
}
К вашему сведению, как показано в приведенном выше итеративном решении, fact
функция не может вычислить точный факториал чисел выше 65 (на самом деле, даже больше 20), потому что встроенный тип Java long
будет переполняться. Рефакторинг, fact
чтобы он возвращал a BigInteger
вместо long
, также дал бы точные результаты для больших входных данных.