Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов времени выполнения в 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
, также дал бы точные результаты для больших входных данных.
Ответы:
Хм ... это работает для меня и с гораздо менее чем 999 МБ стека:
(Windows JDK 7, клиентская виртуальная машина сборки 17.0-b05 и Linux JDK 6 - та же информация о версии, которую вы опубликовали)
источник
Я полагаю, вы вычислили "глубину 1024" по повторяющимся строкам в трассировке стека?
Очевидно, что длина массива трассировки стека в Throwable ограничена 1024 значением. Попробуйте выполнить следующую программу:
источник
Если вы хотите поиграть с размером стека потоков, вам следует взглянуть на параметр -Xss в JVM Hotspot. Это может быть что-то другое на виртуальных машинах, отличных от Hotspot, поскольку параметры -X для JVM зависят от распределения, IIRC.
На Hotspot это выглядит так,
java -Xss16M
будто вы хотите сделать размер 16 мегабайт.Введите,
java -X -help
если вы хотите увидеть все параметры JVM для конкретного дистрибутива, которые вы можете передать. Я не уверен, работает ли это так же на других JVM, но он печатает все параметры, специфичные для Hotspot.Как бы то ни было, я бы рекомендовал ограничить использование рекурсивных методов в Java. Это не слишком хорошо для их оптимизации - например, JVM не поддерживает хвостовую рекурсию (см. «Предотвращает ли JVM оптимизацию хвостового вызова?» ). Попробуйте выполнить рефакторинг приведенного выше факториального кода, чтобы использовать цикл while вместо рекурсивных вызовов методов.
источник
Единственный способ контролировать размер стека в процессе - это начать заново
Thread
. Но вы также можете управлять, создав самовызывающийся суб-процесс Java с-Xss
параметром.источник
java -Xss...
.Добавить эту опцию
к вашей команде spark-submit исправит эту проблему.
источник
Трудно найти разумное решение, поскольку вы стремитесь избегать всех разумных подходов. Рефакторинг одной строки кода - разумное решение.
Примечание. Использование -Xss устанавливает размер стека каждого потока, что является очень плохой идеей.
Другой подход - это манипулирование байтовым кодом для изменения кода следующим образом;
каждый ответ для n> 127 равен 0. Это позволяет избежать изменения исходного кода.
источник
fact
функцию в вопросе можно реорганизовать, чтобы использовать гораздо меньше места в стеке.Weird! Вы говорите, что хотите сгенерировать рекурсию с глубиной 1 << 15 ??? !!!!
Я бы посоветовал НЕ пробовать. Размер стопки будет
2^15 * sizeof(stack-frame)
. Я не знаю, какой размер кадра стека, но 2 ^ 15 составляет 32,768. В значительной степени ... Ну, если он остановится на 1024 (2 ^ 10), вам придется увеличить его в 2 ^ 5 раз, это в 32 раза больше, чем при ваших фактических настройках.источник
Другие плакаты указали, как увеличить память и что вы можете запоминать звонки. Я бы посоветовал для многих приложений использовать формулу Стирлинга для аппроксимации больших n! очень быстро, практически без использования памяти.
Взгляните на этот пост, в котором есть анализ функции и кода:
http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/
источник
Я сделал упражнение Anagram , которое похоже на задачу « Смена счета», но с 50 000 номиналами (монетами). Я не уверен, что это можно делать итеративно , мне все равно. Я просто знаю, что опция -xss не действует - я всегда терпел неудачу после 1024 кадров стека (возможно, scala плохо выполняет доставку в java или ограничение printStackTrace. Я не знаю). Как бы то ни было, это плохой вариант. Вы не хотите, чтобы все потоки в приложении были чудовищными. Однако я провел несколько экспериментов с новым потоком (размером стека). Это действительно работает,
Вы видите, что стек может экспоненциально расти глубже, чем больше стека будет выделено потоку.
источник