Como aumentar o tamanho da pilha Java?

123

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.

pts
fonte
Parece mais simples do que é. fact () é chamado 32K vezes recursivamente. Isso deve ter menos de 1 MB de pilha. : - /
Aaron Digulla
@ Aaron: + Sobrecarga de função, que é muito
halfdan
4
Além de seus problemas de pilha. note que você está explodindo suas longas e ints. 1 << 4 é o valor máximo que eu posso usar antes de ficar negativo e depois chegar a 0. Tente usar o BigInteger
Sean
Não tenho certeza se a sobrecarga da função é realmente muito grande - acho que você ainda deve poder fazer 2 ^ 15 chamadas na ordem de alguns megabytes de espaço na pilha.
Neil Coffey
7
Nota: Você está configurando o tamanho da pilha de cada encadeamento e produzindo um resultado sem sentido, tudo para evitar a refatoração de uma linha de código. Fico feliz que você tenha suas prioridades resolvidas. : P
Peter Lawrey

Respostas:

78

Hmm ... funciona para mim e com muito menos que 999 MB de pilha:

> java -Xss4m Test
0

(Windows JDK 7, compilação VM cliente 17.0-b05 e Linux JDK 6 - informações da mesma versão que você postou)

Jon Skeet
fonte
1
provavelmente foi para o meu comentário, excluí-o quando percebi a mesma coisa que Neil postou.
Sean
Graças a esta pergunta e sua resposta, consegui concluir minha tarefa. Minha função DFS teve que recursar em um gráfico com ~ 10 ^ 5 vértices. Finalmente, trabalhou com -Xss129m: D
bholagabbar
11

Suponho que você calculou a "profundidade de 1024" pelas linhas recorrentes no rastreamento de pilha?

Obviamente, o comprimento da matriz de rastreamento de pilha no Throwable parece estar limitado a 1024. Tente o seguinte programa:

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);
    }
}
Jay
fonte
9

Se você deseja jogar com o tamanho da pilha de encadeamentos, consulte a opção -Xss na JVM do Hotspot. Pode ser algo diferente em VMs sem Hotspot, pois os parâmetros -X para a JVM são específicos da distribuição, IIRC.

No Hotspot, parece java -Xss16Mque você deseja fazer o tamanho 16 megas.

Digite java -X -helpse você deseja ver todos os parâmetros JVM específicos da distribuição que você pode transmitir. Não tenho certeza se isso funciona da mesma maneira em outras JVMs, mas ele imprime todos os parâmetros específicos do Hotspot.

Pelo que vale a pena - eu recomendaria limitar o uso de métodos recursivos em Java. Não é muito bom para otimizá-los - para um, a JVM não suporta recursão de cauda (consulte A JVM evita otimizações de chamada de cauda? ). Tente refatorar seu código fatorial acima para usar um loop while em vez de chamadas de método recursivas.

baleia
fonte
8

A única maneira de controlar o tamanho da pilha no processo é iniciar um novo Thread. Mas você também pode controlar criando um processo sub Java de chamada automática com o -Xssparâmetro

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);
        }
    }

}
Dennis C
fonte
Obrigado por esta resposta informativa, é bom saber sobre as opções além de java -Xss....
pts
1
Fiquei empolgado com isso, mas depois de ler docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#Thread - o construtor de tamanho grande - a emoção foi embora.
kellogs
Gostaria de saber quais plataformas são elas quando o documento diz apenas - "Em algumas plataformas"
Dennis C
3

Adicione esta opção

--driver-java-options -Xss512m

ao seu comando de envio de spark corrigirá esse problema.

Guibin Zhang
fonte
2

É difícil dar uma solução sensata, pois você deseja evitar todas as abordagens sãs. Refatorar uma linha de código é a solução senível.

Nota: O uso de -Xss define o tamanho da pilha de cada encadeamento e é uma péssima idéia.

Outra abordagem é a manipulação de código de bytes para alterar o código da seguinte maneira;

public static long fact(int n) { 
    return n < 2 ? n : n > 127 ? 0 : n * fact(n - 1); 
}

dada todas as respostas para n> 127 é 0. Isso evita a alteração do código fonte.

Peter Lawrey
fonte
1
Obrigado por apontar que definir um tamanho de pilha alto desperdiçaria memória para threads que não precisam dele. Também obrigado por apontar que a factfunção na pergunta pode ser refatorada para usar muito menos espaço na pilha.
pts
1
@pts, seus agradecimentos são anotados. Eu acho que essa é uma pergunta sensata, dado um caso de uso muito mais complexo, mas esses são muito raros.
Peter Lawrey
0

Esquisito! Você está dizendo que deseja gerar uma recursão de 1 << 15 de profundidade ??? !!!!

Eu sugiro que NÃO tente. O tamanho da pilha será 2^15 * sizeof(stack-frame). Não sei qual é o tamanho do quadro de pilha, mas 2 ^ 15 é 32.768. Praticamente ... Bem, se parar em 1024 (2 ^ 10), você terá que aumentá-lo 2 ^ 5 vezes, 32 vezes maior do que na configuração atual.

helios
fonte
0

Outros pôsteres indicaram como aumentar a memória e que você pode memorizar as chamadas. Eu sugiro que, para muitas aplicações, você possa usar a fórmula de Stirling para aproximar n grande! muito rapidamente, sem quase nenhuma pegada de memória.

Dê uma olhada neste post, que tem algumas análises da função e do código:

http://threebrothers.org/brendan/blog/stirlings-approximation-formula-clojure/

fuga
fonte
0

Eu fiz um excersize de Anagram , que é como o problema de mudança de contagem, mas com 50 000 denominações (moedas). Não tenho certeza de que isso possa ser feito iterativamente , não me importo. Eu só sei que a opção -xss não teve efeito - sempre falhava após 1024 quadros de pilha (pode ser que o scala faça um trabalho ruim ao entregar para java ou a limitação printStackTrace. Não sei). Esta é uma opção ruim, como explicado de qualquer maneira. Você não deseja que todos os threads no aplicativo sejam monstruosos. No entanto, fiz alguns experimentos com o novo Thread (tamanho da pilha). Isso funciona de fato,

  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)

Você vê que a pilha pode crescer exponencialmente mais profunda com exponencialmente mais pilha alocada ao encadeamento.

Val
fonte