Da quale versione del kernel / libc di Linux è sicuro Java Runtime.exec () per quanto riguarda la memoria?

StackOverflow https://stackoverflow.com/questions/209875

Domanda

Al lavoro una delle nostre piattaforme target è un mini-server a risorse limitate che esegue Linux (kernel 2.6.13, distribuzione personalizzata basata su un vecchio Fedora Core). L'applicazione è scritta in Java (Sun JDK 1.6_04). Il killer Linux OOM è configurato per terminare i processi quando l'utilizzo della memoria supera i 160 MB. Anche con un carico elevato, la nostra applicazione non supera mai i 120 MB e insieme ad altri processi nativi attivi restiamo entro il limite OOM.

Tuttavia, risulta che il metodo Java Runtime.getRuntime (). exec (), il modo canonico per eseguire processi esterni da Java, ha un implementazione particolarmente sfortunata su Linux che fa sì che i processi figlio generati richiedano (temporaneamente) la stessa quantità di memoria del processo genitore poiché lo spazio degli indirizzi viene copiato. Il risultato netto è che la nostra applicazione viene uccisa dal killer OOM non appena eseguiamo Runtime.getRuntime (). Exec ().

Attualmente ci aggiriamo facendo in modo che un programma nativo separato esegua tutti i comandi esterni e comunichiamo con quel programma su un socket. Questo è meno che ottimale.

Dopo la pubblicazione di questo problema online ho ricevuto feedback che indica che ciò non dovrebbe avvenire su "più recente" versioni di Linux poiché implementano il metodo posix fork () usando copy-on-write, il che significa presumibilmente che copierà solo le pagine che deve modificare quando è richiesto invece dell'intero spazio degli indirizzi immediatamente.

Le mie domande sono:

  • È vero?
  • È qualcosa nel kernel, l'implementazione di libc o da qualche altra parte?
  • Da quale versione del kernel / libc / qualunque sia disponibile copy-on-write per fork ()?
È stato utile?

Soluzione

Questo è praticamente il modo in cui * nix (e Linux) hanno funzionato dall'alba dei tempi (o almeno all'alba di mmus).

Per creare un nuovo processo su * nix chiamate fork (). fork () crea una copia del processo di chiamata con tutti i suoi mapping di memoria, descrittori di file, ecc. I mapping di memoria vengono eseguiti copia su scrittura, quindi (in casi ottimali) in realtà non viene copiata alcuna memoria, ma solo i mapping. Una seguente chiamata exec () sostituisce il mapping di memoria corrente con quello del nuovo eseguibile. Quindi, fork () / exec () è il modo in cui crei un nuovo processo ed è quello che utilizza la JVM.

L'avvertimento è con enormi processi su un sistema occupato, il genitore potrebbe continuare a funzionare per un po 'prima che il figlio exec () stia causando la copia di un'enorme quantità di memoria a causa della copia su scrittura. Nelle macchine virtuali, la memoria può essere spostata molto per facilitare un garbage collector che produce ancora più copie.

La soluzione alternativa " è fare ciò che hai già fatto, creare un processo leggero esterno che si occupa di generare nuovi processi - o utilizzare un approccio più leggero di fork / exec per generare processi (cosa che Linux non ha - e richiederebbe comunque un cambiamento in la stessa jvm). Posix specifica la funzione posix_spawn (), che in teoria può essere implementata senza copiare il mapping di memoria del processo di chiamata - ma su Linux non lo è.

Altri suggerimenti

Beh, personalmente dubito che ciò sia vero, dal momento che il fork di Linux () viene eseguito tramite copy-on-write poiché Dio sa quando (almeno, i kernel 2.2.x lo avevano, ed era da qualche parte nel 199x).

Poiché si ritiene che il killer OOM sia uno strumento piuttosto grezzo che è noto per funzionare in modo errato (fe, non è necessario che uccida il processo che effettivamente ha allocato la maggior parte della memoria) e che dovrebbe essere usato solo come ultimo resport, è non mi è chiaro perché lo hai configurato per sparare su 160M.

Se vuoi imporre un limite sull'allocazione di memoria, allora ulimit è tuo amico, non OOM.

Il mio consiglio è di lasciare solo OOM (o disabilitarlo del tutto), configurare ulimits e dimenticare questo problema.

Sì, questo è assolutamente il caso anche con le nuove versioni di Linux (siamo su Red Hat 5.2 a 64 bit). Ho avuto un problema con i sottoprocessi a esecuzione lenta per circa 18 mesi e non avrei mai potuto capire il problema fino a quando non ho letto la tua domanda ed eseguito un test per verificarlo.

Abbiamo una scatola da 32 GB con 16 core e se eseguiamo la JVM con impostazioni come -Xms4g e -Xmx8g ed eseguiamo sottoprocessi utilizzando Runtime.exec () con 16 thread, non saremo in grado di eseguire il nostro processo più velocemente di circa 20 chiamate di processo al secondo.

Prova con la semplice " date " comando in Linux circa 10.000 volte. Se aggiungi il codice di profilazione per vedere cosa sta succedendo, inizia rapidamente ma rallenta nel tempo.

Dopo aver letto la tua domanda, ho deciso di provare a ridurre le impostazioni di memoria a -Xms128m e -Xmx128m. Ora il nostro processo viene eseguito a circa 80 chiamate al secondo. Le impostazioni della memoria JVM erano tutto ciò che ho cambiato.

Non sembra che stia succhiando la memoria in modo tale che io abbia mai esaurito la memoria, anche quando l'ho provato con 32 thread. È solo la memoria aggiuntiva che deve essere allocata in qualche modo, il che comporta un pesante costo di avvio (e forse l'arresto).

Comunque, sembra che ci dovrebbe essere un'impostazione per disabilitare questo comportamento Linux o forse anche nella JVM.

1: Sì. 2: Questo è diviso in due passaggi: Qualsiasi chiamata di sistema come fork () è racchiusa dal glibc al kernel. La parte del kernel della chiamata di sistema è in kernel / fork.c 3: non lo so. Scommetto che il tuo kernel ce l'ha.

Il killer OOM entra in azione quando la memoria bassa è minacciata su scatole a 32 bit. Non ho mai avuto problemi con questo, ma ci sono modi per tenere a bada OOM. Questo problema potrebbe essere un problema di configurazione OOM.

Dato che stai usando un'applicazione Java, dovresti considerare di passare a Linux a 64 bit. Questo dovrebbe sicuramente risolverlo. La maggior parte delle app a 32 bit può essere eseguita su un kernel a 64 bit senza problemi purché siano installate le librerie pertinenti.

Potresti anche provare il kernel PAE per fedora a 32 bit.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top