2010-05-20 12 views
33

Ho una raccolta di webapp in esecuzione sotto tomcat. Tomcat è configurato per avere fino a 2 GB di memoria usando l'argomento -Xmx.Java Runtime.getRuntime(). Exec() alternative

Molti dei webapps necessità di eseguire un compito che finisce per fare uso del codice seguente:

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

Il problema che stiamo avendo è legato al modo in cui si sta creato questo "bambino-processo" su Linux (Redhat 4.4 e Centos 5.4).

È a mia conoscenza che una quantità di memoria pari alla quantità utilizzata da Tomcat deve essere libera nel pool di memoria di sistema fisica (non swap) inizialmente per la creazione di questo processo figlio. Quando non abbiamo abbastanza memoria fisica libera, stiamo ottenendo questo:

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

Le mie domande sono:

1) E 'possibile rimuovere il requisito per una quantità di memoria pari al genitore processo di essere libero nella memoria fisica? Sto cercando una risposta che mi permetta di specificare quanta memoria riceve il processo figlio o di consentire a java on linux di accedere alla memoria di swap.

2) Quali sono le alternative a Runtime.getRuntime(). Exec() se non esiste alcuna soluzione al numero 1? Ho potuto solo pensare a due, nessuno dei quali è molto desiderabile. JNI (molto poco desiderabile) o riscrivendo il programma che stiamo chiamando in java e rendendolo il proprio processo che la webapp comunica in qualche modo. Ci devono essere altri.

3) C'è un altro lato di questo problema che non vedo che potrebbe risolvere il problema? Ridurre la quantità di memoria utilizzata da tomcat non è un'opzione. Aumentare la memoria sul server è sempre un'opzione, ma sembra più un cerotto.

server sono in esecuzione Java 6.

EDIT: Vorrei specificare che io non sto cercando una correzione specifica Tomcat. Questo problema può essere visto con qualsiasi delle applicazioni java che abbiamo in esecuzione sul server web (ce ne sono molte). Ho semplicemente usato Tomcat come esempio perché molto probabilmente avrà la maggior parte della memoria assegnata ad esso ed è dove abbiamo effettivamente visto l'errore la prima volta. È un errore riproducibile.

MODIFICA: Alla fine, abbiamo risolto questo problema riscrivendo ciò che stava facendo la chiamata di sistema in java. Sento che siamo stati fortunati ad essere in grado di farlo senza effettuare ulteriori chiamate di sistema. Non tutti i processi saranno in grado di farlo, quindi mi piacerebbe comunque vedere una soluzione reale a questo.

+1

Correlato: http://stackoverflow.com/questions/209875/from-what-linux-kernel-libc-version-is-java-runtime-exec-safe-with-regards-to-m – BalusC

+0

I didn ' In realtà vedo qualcosa in quell'articolo che ha risposto alle mie domande. Un'azione che ho visto è stata "diminuire la quantità di memoria utilizzata dal processo principale" (non è un'opzione per noi), sia con ulimit che con java opts. L'altro era lo stesso della risposta di Luca sopra, che è di fare un processo separato che utilizza meno memoria. Questo è lontano dall'ideale, ma almeno plausibile – twilbrand

+0

Cosa JVM stai usando (Sun, OpenJDK ...?) Questo non aiuta? http://stackoverflow.com/questions/1124771/how-to-solve-java-io-ioexception-error12-cannot-allocate-memory-calling-runt – leonbloy

risposta

5

Provare a utilizzare ProcessBuilder. I documenti dicono che è il modo "preferito" per avviare un processo secondario in questi giorni. Si dovrebbe anche considerare l'utilizzo della mappa dell'ambiente (i documenti sono nel collegamento) per specificare le quote di memoria per il nuovo processo. Sospetto (ma non lo so per certo) che la ragione per cui ha bisogno di tanta memoria è che eredita le impostazioni dal processo di tomcat. L'utilizzo della mappa dell'ambiente dovrebbe consentire di ignorare tale comportamento. Tuttavia, si noti che l'avvio di un processo è molto specifico del sistema operativo, quindi YMMV.

+0

Ho esaminato ProcessBuilder e in particolare la sua capacità di modificare l'ambiente. Puoi farlo anche con Runtime. Tuttavia, non sono riuscito a trovare alcuna informazione su esattamente quale variabile ambientale dovrei cambiare per mantenere bassa la memoria iniziale. Ho stampato gli ambienti correnti di alcuni processi e un parametro di modifica della memoria non era evidente. Puoi indicare come posso controllare l'allocazione della memoria con le variabili di ambiente?In particolare in Linux? – twilbrand

+3

Per quanto ho capito, il 'ProcessBuilder' soffre degli stessi vincoli di memoria su Linux. – kongo09

+2

La ragione per cui ha bisogno di così tanta memoria è che l'algoritmo standard "fork" per lo spinning di un nuovo processo fa sì che il processo di esecuzione sia completamente duplicato, temporaneamente. Si dice che questo è l'unico modo multipiattaforma per avviare i processi secondari. Per quanto ho potuto constatare, ProcessBuilder fornisce solo la convenienza - in realtà non risolve questo problema. – robert

2

I think questo è un problema unix fork(), il requisito di memoria deriva dal modo in cui fork() funziona: prima clona l'immagine del processo figlio (a qualsiasi dimensione attualmente è) quindi sostituisce l'immagine principale con l'immagine del bambino.So che su Solaris c'è un modo per controllare questo comportamento, ma non so a priori cosa sia.

Aggiornamento: Questo è già spiegato nel From what Linux kernel/libc version is Java Runtime.exec() safe with regards to memory?

+0

In realtà non ho visto nulla in quell'articolo che abbia risposto alle mie domande. Un'azione che ho visto è stata "diminuire la quantità di memoria utilizzata dal processo principale" (non è un'opzione per noi), sia con ulimit che con java opts. L'altro era lo stesso della risposta di Luca sopra, che è di fare un processo separato che utilizza meno memoria. Questo è tutt'altro che ideale, ma almeno plausibile. – twilbrand

5

ho trovato una soluzione in questo article, in sostanza l'idea è che si crea un processo nelle prime fasi del all'avvio dell'applicazione che si comunica con (tramite l'ingresso flussi) e quindi il sottoprocesso esegue i comandi per te.

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

allora si farebbe un programma di aiuto Java

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

quello che abbiamo fatto è essenzialmente fare un po 'server 'exec' per l'applicazione. Se si inizializza la classe ProcessHelper nella fase iniziale nella propria applicazione, questo processo verrà creato con successo, quindi sarà sufficiente eseguire il pipe dei comandi, poiché il secondo processo è molto più piccolo e dovrebbe sempre avere successo.

È inoltre possibile rendere il protocollo un po 'più approfondito, ad esempio restituire i codici di uscita, notificare errori e così via.

+0

Questo non funzionerà in modo appropriato per una webapp. La webapp viene distribuita in qualsiasi momento durante l'esecuzione del server, consumando qualsiasi quantità di memoria in quel momento. –

+0

@Arne Burmeister: è vero, se lo stai eseguendo su un'istanza TomCat condivisa, potresti avere una quantità arbitrariamente grande di allocata al processo al momento dell'esecuzione. Ma se stai utilizzando un TomCat Server non condiviso (questa è l'unica applicazione) e puoi collegarti all'avvio dell'applicazione per eseguirlo, dovrebbe funzionare. – luke

+0

Come recentemente affermato sopra, non sto cercando una risposta specifica per il tomcat. Abbiamo tomcat con più webapps e molti altri processi Java che potrebbero potenzialmente vedere lo stesso problema. – twilbrand

1

Questo mi aiuterebbe a pensare. So che questo è un thread vecchio, solo per i futuri ref ... http://wrapper.tanukisoftware.com/doc/english/child-exec.html

+0

Sfortunatamente, questo fa parte della Professional Edition, che è costosa, se questa è l'unica cosa di cui hai bisogno. Conosci qualche sostituto gratuito? – kongo09

+0

Prova questa alternativa gratuita a Tanuki Wrapper: http://sourceforge.net/projects/yajsw/forums/forum/810311/topic/4423982 – kongo09

1

Un'altra alternativa è avere un processo di esecuzione separato in esecuzione che guarda un file o qualche altro tipo di "coda". Si aggiunge il comando desiderato a quel file e il server exec lo rileva, esegue il comando e in qualche modo scrive i risultati in un'altra posizione che è possibile ottenere.

+0

La chiave è che il processo di utente exec ha un ingombro ridotto, e quindi può forchetta senza problemi. – Muhd

+0

L'utilizzo di un file o di un altro punto di ingresso visibile pubblicamente sembra possa introdurre problemi di sicurezza, no? Lasciando che un utente malintenzionato esegua codice arbitrario come utente (utente tomcat in questo caso). Un socket con una sorta di protocollo di autenticazione potrebbe essere migliore. E in pratica, quel protocollo di autenticazione potrebbe anche essere SSH ... – Jolta

1

La soluzione migliore che ho trovato finora utilizzava la shell sicura con chiavi pubbliche. Utilizzando http://www.jcraft.com/jsch/ ho creato una connessione a localhost ed eseguito i comandi in questo modo. Forse ha un po 'più di overhead, ma per la mia situazione ha funzionato come un fascino.

Problemi correlati