Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов времени выполнения в 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, также дал бы точные результаты для больших входных данных.