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

Я задал этот вопрос, чтобы узнать, как увеличить размер стека вызовов во время выполнения в JVM. У меня есть ответ на это, и у меня также есть много полезных ответов и комментариев, касающихся того, как Java обрабатывает ситуацию, когда необходим большой стек времени выполнения. Я расширил свой вопрос сводкой ответов.

Первоначально я хотел увеличить размер стека JVM, чтобы такие программы, как запускаются без StackruError,

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... иногда удается, а иногда дает StackruError, Например, для 1 << 20, -Xss18m хватило на 7 пробегов из 10, и -Xss19m не всегда было достаточно, но -Xss20m было достаточно (всего 100 пробегов из 100). Вызывает ли это недетерминированное поведение сборка мусора, включение JIT или что-то еще?

Трассировка стека напечатана на StackruError (и, возможно, также в других исключениях) показывает только самые последние 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 так что это вернет BigInteger вместо long даст точные результаты для больших входов, а также.

9 ответов

Решение

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

> java -Xss4m Test
0

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

Я предполагаю, что вы вычислили "глубину 1024" по повторяющимся строкам в трассировке стека?

Очевидно, что длина массива стека трассировки в Throwable ограничена 1024. Попробуйте следующую программу:

public class Test {

    public static void main(String[] args) {

        try {
            System.out.println(fact(1 << 15));
        }
        catch (StackruError 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);
    }
}

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

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

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

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

Единственный способ контролировать размер стека в процессе - это запустить новый 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 (StackruError 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 (StackruError e) {
            System.err.println("true recursion level was " + level);
            System.err.println("reported recursion level was "
                    + e.getStackTrace().length);
        }
    }

}

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

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

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

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

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

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

--driver-java-options -Xss512m

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

Я сделал Ansgram excersize, что похоже на проблему изменения количества, но с 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: StackruError => // 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)

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

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

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

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

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

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

Другие вопросы по тегам