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 TTacima, 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 naumentada:
- -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, -Xss18mfoi suficiente em 7 corridas de 10 e -Xss19mnem sempre foi suficiente, mas -Xss20mfoi 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 Stackobjeto eg , que é preenchido na pilha em vez de na pilha de tempo de execução). Para esta factfunçã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 factfunçã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 longestouraria. A refatoração factpara retornar um em BigIntegervez de longproduziria resultados exatos para entradas grandes também.