Alternativas Java Runtime.getRuntime (). Exec ()

Eu tenho uma coleção de webapps que estão sendo executados no tomcat. O Tomcat está configurado para ter até 2 GB de memory usando o argumento -Xmx.

Muitas das aplicações web precisam realizar uma tarefa que acaba fazendo uso do seguinte código:

Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(command); process.waitFor(); ... 

O problema que estamos tendo está relacionado à maneira como esse “processo-filho” está sendo criado no Linux (Redhat 4.4 e Centos 5.4).

É meu entendimento que uma quantidade de memory igual à quantidade que o tomcat está usando precisa estar livre no pool de memory do sistema físico (não-swap) inicialmente para que esse processo filho seja criado. Quando não temos memory física livre suficiente, estamos percebendo isso:

  java.io.IOException: error=12, Cannot allocate memory at java.lang.UNIXProcess.(UNIXProcess.java:148) at java.lang.ProcessImpl.start(ProcessImpl.java:65) at java.lang.ProcessBuilder.start(ProcessBuilder.java:452) ... 28 more 

Minhas perguntas são:

1) É possível remover o requisito de que uma quantidade de memory igual ao processo pai esteja livre na memory física? Eu estou procurando uma resposta que me permite especificar a quantidade de memory que o processo filho recebe ou permitir que o java no Linux acesse a memory swap.

2) Quais são as alternativas para Runtime.getRuntime (). Exec () se não existe solução para # 1? Eu só conseguia pensar em dois, nenhum dos quais é muito desejável. JNI (muito indesejável) ou reescrevendo o programa que estamos chamando em java e tornando o processo próprio que a webapp comunica de alguma forma. Tem que haver outros.

3) Existe outro lado para este problema que eu não estou vendo que poderia potencialmente consertá-lo? Diminuir a quantidade de memory usada pelo tomcat não é uma opção. Aumentar a memory no servidor é sempre uma opção, mas parece mais um band-aid.

Servidores estão executando o java 6.

EDIT: eu deveria especificar que eu não estou procurando uma correção específica de tomcat. Esse problema pode ser visto em qualquer um dos aplicativos java que estamos executando no servidor da Web (existem vários). Eu simplesmente usei o tomcat como um exemplo porque ele provavelmente terá a maior parte da memory alocada para ele e é onde realmente vimos o erro na primeira vez. É um erro reproduzível.

EDIT: No final, resolvemos esse problema reescrevendo o que a chamada do sistema estava fazendo em java. Eu sinto que tivemos muita sorte em poder fazer isso sem fazer chamadas adicionais ao sistema. Nem todos os processos poderão fazer isso, então ainda adoraria ver uma solução real para isso.

Eu encontrei uma solução neste artigo , basicamente, a idéia é que você crie um processo no início da boot do seu aplicativo com o qual você se comunica (por meio de streams de input) e, em seguida, esse subprocess executa seus comandos para você.

 //you would probably want to make this a singleton public class ProcessHelper { private OutputStreamWriter output; public ProcessHelper() { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec("java ProcessHelper"); output = new OutputStreamWriter(process.getOutputStream()); } public void exec(String command) { output.write(command, 0, command.length()); } } 

então você faria um programa java auxiliar

 public class ProcessHelper { public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String command; while((command = in.readLine()) != null) { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec(command); } } } 

O que basicamente fizemos foi criar um pequeno servidor ‘exec’ para sua aplicação. Se você inicializar sua class ProcessHelper logo no início do seu aplicativo, ele criará esse processo com sucesso, então você simplesmente enviará comandos para ele, porque o segundo processo é muito menor e deve sempre ser bem-sucedido.

Você também pode tornar seu protocolo um pouco mais detalhado, como retornar códigos de saída, notificar erros e assim por diante.

Tente usar um ProcessBuilder . Os Documentos dizem que essa é a maneira “preferida” de iniciar um subprocess nos dias de hoje. Você também deve considerar o uso do mapa de ambiente (os documentos estão no link) para especificar as permissions de memory para o novo processo. Eu suspeito (mas não sei ao certo) que a razão pela qual ela precisa de muita memory é que ela está herdando as configurações do processo do tomcat. Usar o mapa do ambiente deve permitir que você substitua esse comportamento. No entanto, observe que iniciar um processo é muito específico do sistema operacional, portanto, YMMV.

Eu acho que isso é um problema unix fork (), o requisito de memory vem da maneira como fork () funciona – primeiro clona a imagem do processo filho (em qualquer tamanho que seja atualmente) e substitui a imagem pai pela imagem filha. Eu sei que no Solaris há alguma maneira de controlar esse comportamento, mas não sei de antemão o que é.

Update : Isso já está explicado em De que versão do kernel Linux / libc é Java Runtime.exec () segura em relação à memory?

Isso ajudaria eu acho. Eu sei que este é um segmento antigo, apenas para futuros refs … http://wrapper.tanukisoftware.com/doc/english/child-exec.html

Outra alternativa é ter um processo exec separado que observe um arquivo ou algum outro tipo de “fila”. Você anexa o comando desejado a esse arquivo, e o servidor exec o localiza, executa o comando e, de alguma forma, grava os resultados em outro local que você pode obter.

A melhor solução que encontrei até agora foi usando shell seguro com chaves públicas. Usando o http://www.jcraft.com/jsch/, criei uma conexão com o localhost e executei os comandos desse jeito. Talvez tenha um pouco mais de sobrecarga, mas para minha situação funcionou como um encanto.

Intereting Posts