As tarefas <javac> do Ant lançam StackOverflowException
Pergunta
Estou tentando compilar mais de 100 aulas de Java de diferentes pacotes de um diretório limpo (sem compilos incrementais) usando as seguintes tarefas de formiga:
<target name="-main-src-depend">
<depend srcdir="${src.dir}"
destdir="${bin.dir}"
cache="${cache.dir}"
closure="true"/>
</target>
<target name="compile" depends="-main-src-depend"
description="Compiles the project.">
<echo>Compiling</echo>
<javac target="${javac.target}"
source="${javac.source}"
debug="${javac.debug}"
srcdir="${src.dir}"
destdir="${bin.dir}">
<classpath>
<path refid="runtime.classpath"/>
<path refid="compile.classpath"/>
</classpath>
</javac>
</target>
No entanto, na primeira vez que executo a tarefa de compilação, sempre recebo uma StackOverflowException.Se eu executar a tarefa novamente, o compilador fará uma compilação incremental e tudo funcionará bem.Isso é indesejável, pois estamos usando CruiseControl para fazer uma compilação diária automática e isso está causando falsas falhas de compilação.
Como uma solução rápida e simples, criei 2 tarefas separadas, compilando partes do projeto em cada uma.Eu realmente não acho que esta solução será válida à medida que mais classes forem adicionadas no futuro, e não quero adicionar novas tarefas de compilação toda vez que atingirmos o "limite de compilação".
Solução
Será bom saber;O que pode causar ou causar um StackOverFlowerRor durante a compilação do código Java?
É provável que a avaliação da expressão longa no seu arquivo Java consuma muita memória e, como isso está sendo feito em conjunto com a compilação de outras classes, a VM simplesmente fica sem espaço na pilha.Sua classe gerada talvez esteja ultrapassando os limites legais de seu conteúdo.Veja o capítulo 4.10 Limitações da Máquina Virtual Java em A especificação da máquina virtual Java, segunda edição.
Correção 1:refatorar a classe
Como sua classe está sendo gerada, isso pode não ser uma opção.Ainda assim, vale a pena dar uma olhada nas opções que sua ferramenta de geração de classes oferece para ver se ela pode produzir algo menos problemático.
Correção 2:aumentar o tamanho da pilha
Eu penso Kieron tem uma solução quando menciona o argumento -Xss. javac aceita vários argumentos não padrão que variam entre versões e fornecedores de compiladores.
Meu compilador:
$ javac -version
javac 1.6.0_05
Para listar todas as opções, eu usaria estes comandos:
javac -help
javac -X
javac -J-X
EU pensar o limite de pilha para javac é 512 KB por padrão.Você pode aumentar o tamanho da pilha deste compilador para 10 MB com este comando:
javac -J-Xss10M Foo.java
Você pode passar isso em um arquivo Ant com um compilarg elemento aninhado em seu javac tarefa.
<javac srcdir="gen" destdir="gen-bin" debug="on" fork="true">
<compilerarg value="-J-Xss10M" />
</javac>
Outras dicas
<javac srcdir="gen" destdir="gen-bin" debug="on" fork="true">
<compilerarg value="-J-Xss10M" />
</javac>
de comente acima está incorreto.Você precisa de um espaço entre -J e -X, assim:
<javac srcdir="gen" destdir="gen-bin" debug="on" fork="true">
<compilerarg value="-J -Xss10M" />
</javac>
para evitar o seguinte erro:
[javac]
[javac] The ' characters around the executable and arguments are
[javac] not part of the command.
[javac] Files to be compiled:
...[javac]javac:sinalizador inválido:-J-xss1m [javac] Uso:javac
Tente adicionar alguma variação desses atributos ao Formiga javac
tarefa linha:
memoryinitialsize="256M" memorymaximumsize="1024M"
Você também pode tentar fork="true"
, não tenho certeza se isso permite definir valores para pilha e heap (também conhecido como -Xm1024), mas pode ajudar (se funcionaria na linha de comando, mas não no Ant).
[Editar]:Link adicionado - o javac
tarefa parece sugerir que os parâmetros acima exigem que você também defina fork="true"
.
Isso acontece quando você executa o comando javac na linha de comando?Você pode querer experimentar o garfo atributo.
Isso é muito estranho, 100 aulas não são tantas.O que o compilador está fazendo quando a pilha transborda?Existe um rastreamento de pilha útil gerado?O que acontece se você correr javac
diretamente na linha de comando em vez de usar o ant?
Uma solução possível é simplesmente aumentar o tamanho da pilha usando o método -Xss
argumento para a JVM;seja para a JVM em execução ant
ou configurando fork="true"
e um <compilerarg>
no <javac>
tarefa.Na verdade, agora que penso nisso, o problema desaparece apenas colocando o fork="true"
?
Aqui está o que encontrei.Depois de postar minha pergunta, modifiquei a tarefa de compilação com os atributos fork="true"
, memoryinitialsize="256m"
e memorymaximumsize="1024m"
(descobri hoje que isso foi sugerido por Kieron e jmanning2k, obrigado pelo seu tempo).Mesmo assim, isso não resolveu o problema.
Decidi começar a remover classes da árvore de origem para ver se conseguia identificar o problema.Acontece que tínhamos uma classe de cliente Web Service para Eixo 1.4 que foi gerado automaticamente a partir de um arquivo WSDL.Agora, esta classe é um monstro (como em Frankenstein), tem 167 membros de campo (todos eles do tipo String), 167 pares getter/setter (1 para cada campo), um construtor que recebe todos os 167 campos como parâmetros, um método equals que compara todos os 167 campos de uma forma estranha.Para cada campo a comparação é assim:
(this.A == null && other.getA() == null) || (this.A != null && this.A.equals(other.getA()))
O resultado desta comparação é "andado" (&&) com o resultado da comparação do próximo campo e assim por diante.A classe continua com um método hashCode que também usa todos os campos, alguns métodos de serialização XML personalizados e um método que retorna um objeto de metadados específico do Axis que descreve a classe e que também usa todos os membros do campo.
Esta classe nunca é modificada, então apenas coloquei uma versão compilada no classpath da aplicação e o projeto foi compilado sem problemas.
Agora, sei que a remoção desse arquivo de origem única resolveu o problema.No entanto, não tenho absolutamente nenhuma ideia de por que essa classe específica causou o problema.Será bom saber;o que pode causar ou causar um StackOverflowError durante a compilação do código Java?Acho que vou postar essa pergunta.
Para os interessados:
- WindowsXP SP2
- JDK 1.4.2_17 da SUN
- Formiga 1.7.0