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