Как увеличить размер стека Java?
Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов во время выполнения в 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, -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
, также дал бы точные результаты для больших входных данных.
Переведено автоматически
Ответ 1
Хм ... у меня это работает и с гораздо меньшим объемом стека, чем 999 МБ:
> java -Xss4m Test
0
(Windows JDK 7, сборка 17.0-клиентская виртуальная машина b05 и Linux JDK 6 - информация о версии такая же, как вы опубликовали)
Ответ 2
Я предполагаю, что вы вычислили "глубину 1024" по повторяющимся строкам в трассировке стека?
Очевидно, что длина массива трассировки стека в Throwable, похоже, ограничена 1024. Попробуйте следующую программу:
public class Test {
public static void main(String[] args) {
try {
System.out.println(fact(1 << 15));
}
catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was " +
e.getStackTrace().length);
}
}
private static int level = 0;
public static long fact(int n) {
level++;
return n < 2 ? n : n * fact(n - 1);
}
}
Ответ 3
Если вы хотите поиграть с размером стека потоков, вам стоит посмотреть на опцию -Xss в Hotspot JVM. Это может быть что-то другое на виртуальных машинах, отличных от Hotspot, поскольку параметры -X для JVM зависят от дистрибутива, IIRC.
В Hotspot это выглядит следующим образом: java -Xss16M
если вы хотите увеличить размер до 16 мегабайт.
Введите java -X -help
, если вы хотите увидеть все параметры JVM, специфичные для дистрибутива, которые вы можете передать. Я не уверен, работает ли это так же на других JVM, но он выводит все параметры, специфичные для Hotspot.
Как бы то ни было, я бы рекомендовал ограничить использование рекурсивных методов в Java. Оптимизация не слишком хороша - во-первых, JVM не поддерживает хвостовую рекурсию (см. Предотвращает ли JVM оптимизацию хвостовых вызовов?). Попробуйте рефакторинг вашего факториального кода, приведенный выше, чтобы использовать цикл while вместо рекурсивных вызовов методов.
Ответ 4
Единственный способ контролировать размер стека внутри процесса - это запустить новый Thread
. Но вы также можете управлять, создав самозваный процесс sub Java с параметром -Xss
.
public class TT {
private static int level = 0;
public static long fact(int n) {
level++;
return n < 2 ? n : n * fact(n - 1);
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(null, null, "TT", 1000000) {
@Override
public void run() {
try {
level = 0;
System.out.println(fact(1 << 15));
} catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was "
+ e.getStackTrace().length);
}
}
};
t.start();
t.join();
try {
level = 0;
System.out.println(fact(1 << 15));
} catch (StackOverflowError e) {
System.err.println("true recursion level was " + level);
System.err.println("reported recursion level was "
+ e.getStackTrace().length);
}
}
}