Як збільшити розмір стека Java?


123

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

Спочатку я хотів збільшити розмір стека JVM, щоб такі програми, як запуск без a 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...іноді досягає успіху, а іноді і дає a 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), оскільки вбудований тип Java longпереповнюється. Рефакторинг, factщоб він повернувся BigIntegerзамість цього, longдав би точні результати і для великих входів.


Виглядає простіше, ніж це є. факт () називається 32K разів рекурсивно. Це має бути менше 1 МБ стека. : - /
Аарон Дігулла

@Aaron: + Функція накладних витрат, яка є .. ЛОТ
південний день

4
Окрім проблем зі стеком. зауважте, що ви роздуваєте свої довгі та інти. 1 << 4 - максимальне значення, яке я можу використати, перш ніж перейти в негативне значення, а потім - у 0. Спробуйте скористатися BigInteger
Шон

Не впевнений, що функція накладних коштів насправді сильно тахає - я думаю, ви все одно зможете здійснювати 2 ^ 15 дзвінків у порядку декількох мегабайт простору стеку.
Ніл Коффі

7
Примітка. Ви встановлюєте розмір стека кожного потоку і даєте безглузді результати, щоб уникнути рефакторингу одного рядка коду. Я радий, що ти розібрав свої пріоритети. : P
Пітер Лорі

Відповіді:


78

Гм ... це працює для мене і з набагато меншим 999 МБ стека:

> java -Xss4m Test
0

(Windows JDK 7, збірка VM клієнта 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. У VM, що не є Hotspot, може бути щось інше, оскільки параметри -X для JVM є специфічними для розподілу, IIRC.

На точці доступу це виглядає так java -Xss16M якщо ви хочете зробити розмір 16 мег.

Тип java -X -help якщо ви хочете побачити всі параметри JVM, що стосуються розподілу, які ви можете передати. Я не впевнений, чи працює це так само на інших JVM, але він друкує всі параметри Hotspot.

Для чого це варто - я б рекомендував обмежити використання рекурсивних методів на Java. Оптимізація їх не надто велика - для одного JVM не підтримує хвостову рекурсію (див. Чи запобігає JVM оптимізація хвостових викликів? ). Спробуйте перефактурувати свій фактичний код вище, щоб використовувати цикл час, а не рекурсивні виклики методу.


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 - конструктор штабелів - хвилювання пішло.
kellogs

Цікаво, які це платформи, коли в документі сказано лише - "На деяких платформах"
Dennis C

3

Додайте цю опцію

--driver-java-options -Xss512m

до вашої команди на подання іскри виправить цю проблему.


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

Дивно! Ви говорите, що хочете генерувати рекурсію глибиною 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/


0

Я зробив Anagram excersize , що схоже на проблему Count Count, але з 50 000 номіналів (монет). Я не впевнений, що це можна зробити ітераційно , мені все одно. Я просто знаю, що опція -xss не мала ефекту - я завжди провалювався після 1024 фреймів стека (можливо, скала робить погану роботу, доставляючи до 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)

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.