Как увеличить размер стека Java?

124

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

PTS
источник
Выглядит проще, чем есть на самом деле. fact () вызывается 32К раз рекурсивно. Это должно быть меньше 1 МБ стека. : - /
Аарон Дигулла
@Aaron: + Накладные расходы на функции, которые ..
МНОГОЧИСЛЕННЫЕ
4
Помимо проблем со стеком. обратите внимание, что вы взрываете свои длинные и целые числа. 1 << 4 - это максимальное значение, которое я могу использовать перед тем, как перейти в отрицательное значение, а затем в 0. Попробуйте использовать BigInteger
Шон
Не уверен, что накладные расходы на функции на самом деле очень большие - я думаю, вы все равно должны иметь возможность выполнять 2 ^ 15 вызовов в порядке нескольких мегабайт пространства стека.
Нил Коффи,
7
Примечание. Вы устанавливаете размер стека для каждого потока и получаете бессмысленный результат, чтобы избежать рефакторинга одной строки кода. Я рад, что вы разобрались со своими приоритетами. : P
Питер Лоури

Ответы:

79

Хм ... это работает для меня и с гораздо менее чем 999 МБ стека:

> java -Xss4m Test
0

(Windows JDK 7, клиентская виртуальная машина сборки 17.0-b05 и Linux JDK 6 - та же информация о версии, которую вы опубликовали)

Джон Скит
источник
1
скорее всего это был мой комментарий, я удалил его, когда понял то же самое, что написал Нил.
Шон
Благодаря этому вопросу и вашему ответу мне удалось выполнить задание. Моя функция DFS должна была рекурсировать на графе с ~ 10 ^ 5 вершинами. Наконец-то это сработало с -Xss129m: D
bholagabbar
11

Я полагаю, вы вычислили "глубину 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);
    }
}
сойка
источник
9

Если вы хотите поиграть с размером стека потоков, вам следует взглянуть на параметр -Xss в JVM Hotspot. Это может быть что-то другое на виртуальных машинах, отличных от Hotspot, поскольку параметры -X для JVM зависят от распределения, IIRC.

На Hotspot это выглядит так, java -Xss16Mбудто вы хотите сделать размер 16 мегабайт.

Введите, java -X -helpесли вы хотите увидеть все параметры JVM для конкретного дистрибутива, которые вы можете передать. Я не уверен, работает ли это так же на других JVM, но он печатает все параметры, специфичные для Hotspot.

Как бы то ни было, я бы рекомендовал ограничить использование рекурсивных методов в Java. Это не слишком хорошо для их оптимизации - например, JVM не поддерживает хвостовую рекурсию (см. «Предотвращает ли JVM оптимизацию хвостового вызова?» ). Попробуйте выполнить рефакторинг приведенного выше факториального кода, чтобы использовать цикл while вместо рекурсивных вызовов методов.

Уэйли
источник
8

Единственный способ контролировать размер стека в процессе - это начать заново Thread. Но вы также можете управлять, создав самовызывающийся суб-процесс 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);
        }
    }

}
Деннис С
источник
Спасибо за информативный ответ, приятно знать о вариантах помимо java -Xss....
птс
1
Я был в восторге от этого, но затем, прочитав docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - конструктор stacksize - волнение прошло.
kellogs
Интересно, что это за платформы, когда в документе только сказано - «На некоторых платформах»
Деннис С.
3

Добавить эту опцию

--driver-java-options -Xss512m

к вашей команде spark-submit исправит эту проблему.

Гуйбинь Чжан
источник
2

Трудно найти разумное решение, поскольку вы стремитесь избегать всех разумных подходов. Рефакторинг одной строки кода - разумное решение.

Примечание. Использование -Xss устанавливает размер стека каждого потока, что является очень плохой идеей.

Другой подход - это манипулирование байтовым кодом для изменения кода следующим образом;

public static long fact(int n) { 
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1); 
}

каждый ответ для n> 127 равен 0. Это позволяет избежать изменения исходного кода.

Питер Лоури
источник
1
Благодарим за указание на то, что установка большого размера стека приведет к потере памяти для потоков, которым она не нужна. Также спасибо за указание на то, что factфункцию в вопросе можно реорганизовать, чтобы использовать гораздо меньше места в стеке.
птс
1
@pts, ваши благодарности отмечены. Я думаю, что это разумный вопрос, учитывая гораздо более сложный вариант использования, но они очень редки.
Питер Лоури
0

Weird! Вы говорите, что хотите сгенерировать рекурсию с глубиной 1 << 15 ??? !!!!

Я бы посоветовал НЕ пробовать. Размер стопки будет 2^15 * sizeof(stack-frame). Я не знаю, какой размер кадра стека, но 2 ^ 15 составляет 32,768. В значительной степени ... Ну, если он остановится на 1024 (2 ^ 10), вам придется увеличить его в 2 ^ 5 раз, это в 32 раза больше, чем при ваших фактических настройках.

Гелиос
источник
0

Другие плакаты указали, как увеличить память и что вы можете запоминать звонки. Я бы посоветовал для многих приложений использовать формулу Стирлинга для аппроксимации больших n! очень быстро, практически без использования памяти.

Взгляните на этот пост, в котором есть анализ функции и кода:

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/

abscondment
источник
0

Я сделал упражнение Anagram , которое похоже на задачу « Смена счета», но с 50 000 номиналами (монетами). Я не уверен, что это можно делать итеративно , мне все равно. Я просто знаю, что опция -xss не действует - я всегда терпел неудачу после 1024 кадров стека (возможно, scala плохо выполняет доставку в java или ограничение printStackTrace. Я не знаю). Как бы то ни было, это плохой вариант. Вы не хотите, чтобы все потоки в приложении были чудовищными. Однако я провел несколько экспериментов с новым потоком (размером стека). Это действительно работает,

  def measureStackDepth(ss: Long): Long = {
    var depth: Long = 0
      val thread: Thread = new Thread(null, new Runnable() {
        override def run() {
          try {
          def sum(n: Long): Long = {depth += 1; if (n== 0) 0 else sum(n-1) + 1}
          println("fact = " + sum(ss * 10))
          } catch {
            case e: StackOverflowError => // eat the exception, that is expected
          }
        }
      }, "deep stack for money exchange", ss)
      thread.start()
      thread.join()
    depth
  }                                               //> measureStackDepth: (ss: Long)Long


  for (ss <- (0 to 10)) println("ss = 10^" +  ss + " allows stack of size " -> measureStackDepth((scala.math.pow (10, ss)).toLong) )
                                                  //> fact = 10
                                                  //| (ss = 10^0 allows stack of size ,11)
                                                  //| fact = 100
                                                  //| (ss = 10^1 allows stack of size ,101)
                                                  //| fact = 1000
                                                  //| (ss = 10^2 allows stack of size ,1001)
                                                  //| fact = 10000
                                                  //| (ss = 10^3 allows stack of size ,10001)
                                                  //| (ss = 10^4 allows stack of size ,1336)
                                                  //| (ss = 10^5 allows stack of size ,5456)
                                                  //| (ss = 10^6 allows stack of size ,62736)
                                                  //| (ss = 10^7 allows stack of size ,623876)
                                                  //| (ss = 10^8 allows stack of size ,6247732)
                                                  //| (ss = 10^9 allows stack of size ,62498160)

Вы видите, что стек может экспоненциально расти глубже, чем больше стека будет выделено потоку.

Val
источник