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