Como aumentar o tamanho da pilha Java?

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 execuções semStackOverflowError.

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 é ajava -Xss... sinalizador de linha de comando com um valor grande o suficiente. Para o programaTT acima, funciona assim com a JVM do OpenJDK:

$ javac TT.java
$ java -Xss4m TT

Uma das respostas também apontou que o-X... sinalizadores são dependentes 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 segmento (veja em uma das respostas como). Isto é recomendadojava -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 o executein aumentou:

-Xss4m pode ser suficiente parafact(1 << 15)-Xss5m pode ser suficiente parafact(1 << 17)-Xss7m pode ser suficiente parafact(1 << 18)-Xss9m pode ser suficiente parafact(1 << 19)-Xss18m pode ser suficiente parafact(1 << 20)-Xss35m pode ser suficiente parafact(1 << 21)-Xss68m pode ser suficiente parafact(1 << 22)-Xss129m pode ser suficiente parafact(1 << 23)-Xss258m pode ser suficiente parafact(1 << 24)-Xss515m pode ser suficiente parafact(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émpode ser o suficiente ao invés debasta, porque o requisito da pilha não é determinístico: executando-o várias vezes com o mesmo arquivo de origem e o mesmo-Xss... às vezes consegue e às vezes produz umStackOverflowError. Por exemplo. para 1 << 20,-Xss18m foi suficiente em 7 corridas em 10, e-Xss19m nem sempre foi suficiente, mas-Xss20m foi suficiente (em todas as 100 execuções em 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 umStackOverflowError (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 ativas e com menos pilha do mesmo algoritmo. Em geral, é possível converter para um conjunto de funções recursivas em funções iterativas (usando umStack objeto, que é preenchido na pilha em vez de na pilha de tempo de execução). Para este particularfact função, é muito fácil convertê-lo. 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, ofact A função não pode calcular o fatorial exato dos números acima de 65 (na verdade, mesmo acima de 20), porque o tipo interno Javalong transbordaria. Refatoraçãofact então retornaria umBigInteger ao invés delong também produziria resultados exatos para grandes insumos.

questionAnswers(9)

yourAnswerToTheQuestion