Как увеличить размер стека 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 выше, она работает следующим образом с 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, или что-то еще, вызывает это недетерминированное поведение?

Трассировка стека, напечатанная на 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));
  }
}

FYI, как показывает приведенное выше итерационное решение, функция fact не может вычислить точный факториал чисел выше 65 (фактически, даже выше 20), потому что встроенный тип long Java будет переполняться. Рефакторинг fact, чтобы он возвращал 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 на JVM Hotspot. Это может быть что-то другое в не-Hotspot VM, поскольку параметры -X для JVM являются специфичными для распространения, IIRC.

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

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

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

Ответ 4

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

}

Ответ 5

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

--driver-java-options -Xss512m

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

Ответ 6

В вашем примере используется рекурсия дерева и интенсивность памяти. Вы можете использовать memoization (сохранение ранее рассчитанных результатов, а не их повторное вычисление) и выйти вперед, не отказываясь полностью от своего рекурсивного решения.

Ответ 7

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

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

Я сделал Anagram excersize, который похож на Count Change проблема, но с 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)

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

Ответ 11

Попробуйте параметр jvm: -Xmx128m