Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов времени выполнения в 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
увеличилось:
fact (1 << 15)
fact (1 << 17)
fact (1 << 18 )
fact (1 << 19)
fact (1 << 20)
fact (1 << 21)
fact (1 << 22)
fact (1 << 23)
fact (1 << 24)
факта (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));
}
}
К вашему сведению, как показано в приведенном выше итеративном решении, функция fact
не может вычислить точный факториал чисел больше 65 (фактически, даже больше 20), потому что Java построила -in тип long
приведет к переполнению. Рефакторинг факта
, чтобы он возвращал BigInteger
вместо long
, также даст точные результаты для больших входных данных.