Fiz essa pergunta para saber como aumentar o tamanho da pilha de chamadas em tempo de execução na JVM. Eu tenho uma resposta para isso e também tenho muitas respostas e comentários úteis relevantes sobre como o Java lida com a situação em que uma grande pilha de tempo de execução é necessária. Estendi minha pergunta com o resumo das respostas.
Originalmente, eu queria aumentar o tamanho da pilha da JVM para que programas como rodem sem 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));
}
}
A configuração correspondente é o java -Xss...
sinalizador da linha de comando com um valor grande o suficiente. Para o programa TT
acima, funciona assim com a JVM do OpenJDK:
$ javac TT.java
$ java -Xss4m TT
Uma das respostas também apontou que os -X...
sinalizadores dependem da implementação. Eu estava usando
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)
Também é possível especificar uma pilha grande apenas para um encadeamento (veja em uma das respostas como). Isso é recomendado java -Xss...
para evitar desperdiçar memória para threads que não precisam dela.
Fiquei curioso sobre o tamanho exato de uma pilha que o programa acima precisa, então eu a executei n
aumentada:
- -Xss4m pode ser suficiente para
fact(1 << 15)
- -Xss5m pode ser suficiente para
fact(1 << 17)
- -Xss7m pode ser suficiente para
fact(1 << 18)
- -Xss9m pode ser suficiente para
fact(1 << 19)
- -Xss18m pode ser suficiente para
fact(1 << 20)
- -Xss35m pode ser suficiente para
fact(1 << 21)
- -Xss68m pode ser suficiente para
fact(1 << 22)
- -Xss129m pode ser suficiente para
fact(1 << 23)
- -Xss258m pode ser suficiente para
fact(1 << 24)
- -Xss515m pode ser suficiente para
fact(1 << 25)
A partir dos números acima, parece que o Java está usando cerca de 16 bytes por quadro de pilha para a função acima, o que é razoável.
A enumeração acima contém pode ser suficiente em vez de é suficiente , porque o requisito da pilha não é determinístico: executá-lo várias vezes com o mesmo arquivo de origem e o mesmo -Xss...
às vezes obtém êxito e às vezes gera a StackOverflowError
. Por exemplo, para 1 << 20, -Xss18m
foi suficiente em 7 corridas de 10 e -Xss19m
nem sempre foi suficiente, mas -Xss20m
foi suficiente (em todas as 100 corridas de 100). A coleta de lixo, a entrada em funcionamento do JIT ou algo mais causa esse comportamento não determinístico?
O rastreamento de pilha impresso em StackOverflowError
(e possivelmente em outras exceções também) mostra apenas os 1024 elementos mais recentes da pilha de tempo de execução. Uma resposta abaixo demonstra como contar a profundidade exata atingida (que pode ser muito maior que 1024).
Muitas pessoas que responderam apontaram que é uma prática de codificação boa e segura considerar implementações alternativas, menos famintas de pilha, do mesmo algoritmo. Em geral, é possível converter um conjunto de funções recursivas em funções iterativas (usando um Stack
objeto eg , que é preenchido na pilha em vez de na pilha de tempo de execução). Para esta fact
função específica , é bastante fácil convertê-la. Minha versão iterativa seria semelhante a:
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));
}
}
Para sua informação, como mostra a solução iterativa acima, a fact
função não pode computar o fatorial exato dos números acima de 65 (na verdade, mesmo acima de 20), porque o tipo interno do Java long
estouraria. A refatoração fact
para retornar um em BigInteger
vez de long
produziria resultados exatos para entradas grandes também.