Как увеличить размер стека Java? - PullRequest
110 голосов
/ 13 сентября 2010

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

К вашему сведению, как показывает приведенное выше итеративное решение, функция fact не может вычислить точный факториал чисел выше 65 (на самом деле, даже выше 20), поскольку встроенный тип Java long будет переполнен. Рефакторинг fact, так что он вернул бы BigInteger вместо long, дал бы точные результаты и для больших входных данных.

Ответы [ 9 ]

70 голосов
/ 13 сентября 2010

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

> java -Xss4m Test
0

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

11 голосов
/ 15 сентября 2010

Полагаю, вы вычислили "глубину 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 голосов
/ 13 сентября 2010

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

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

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

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

8 голосов
/ 17 сентября 2010

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

}
3 голосов
/ 30 ноября 2016

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

--driver-java-options -Xss512m

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

1 голос
/ 18 сентября 2010

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

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

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

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

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

0 голосов
/ 28 июня 2014

Я сделал Размер анаграммы , что похоже на проблему Изменение счета , но с 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)

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

0 голосов
/ 25 ноября 2010

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

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

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

0 голосов
/ 15 сентября 2010

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

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

...