Domanda

Ho un problema con un'applicazione Java in esecuzione sotto Linux.

Quando lancio dell'applicazione, utilizzando la dimensione heap massima predefinita (64 MB), vedo utilizzando l'applicazione cime che 240 MB di memoria virtuale vengono assegnate per l'applicazione. Questo crea alcuni problemi con qualche altro software sul computer, che è relativamente a risorse limitate.

La memoria virtuale riservata non sarà utilizzato in ogni caso, per quanto ho capito, perché una volta che si raggiunge limitare il cumulo di un OutOfMemoryError viene generata. Ho eseguito la stessa applicazione sotto le finestre e vedo che la dimensione della memoria virtuale e la dimensione Heap sono simili.

È comunque ci che posso configurare la memoria virtuale in uso per un processo di Java sotto Linux?

Modifica 1 : Il problema non è l'Heap. Il problema è che se ho impostato un mucchio di 128 MB, ad esempio, ancora Linux alloca 210 MB di memoria virtuale, che non è necessario, mai. **

Modifica 2 : Usando ulimit -v consente di limitare la quantità di memoria virtuale. Se il set di dimensioni è inferiore a 204 MB, quindi l'applicazione non funzionerà, anche se non ha bisogno di 204 MB, solo 64 MB. Quindi voglio capire perché Java richiede tanta memoria virtuale. Può questo essere cambiato?

Modifica 3 : Ci sono diverse altre applicazioni in esecuzione nel sistema, che è incorporato. E il sistema ha un limite di memoria virtuale (dai commenti, dettaglio importante).

È stato utile?

Soluzione

Questa è stata una denuncia di lunga data con Java, ma è in gran parte privo di significato, e di solito basato sulla guardando l'informazioni errate. Il fraseggio solito è qualcosa come "Ciao Mondo su Java richiede 10 megabyte! Perché ha bisogno che?" Bene, ecco un modo per rendere Ciao Mondo su un credito JVM a 64 bit di prendere più di 4 gigabyte ... almeno da una forma di misurazione.

java -Xms1024m -Xmx4096m com.example.Hello

diversi modi per misurare memoria

In Linux, il comando top offre diversi numeri differenti per la memoria. Ecco cosa dice a proposito l'esempio Ciao Mondo:

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 2120 kgregory  20   0 4373m  15m 7152 S    0  0.2   0:00.10 java
  • VIRT è lo spazio di memoria virtuale: la somma di tutto ciò che nella mappa della memoria virtuale (vedi sotto). È in gran parte priva di senso, se non quando non è (vedi sotto).
  • RES è la dimensione resident set: il numero di pagine che sono attualmente residenti in RAM. In quasi tutti i casi, questo è l'unico numero che si dovrebbe usare quando dice "troppo grandi". Ma non è ancora un numero molto buona, soprattutto quando si parla di Java.
  • SHR è la quantità di memoria residente che è condiviso con altri processi. Per un processo Java, questo è in genere limitata alle librerie condivise e jarfiles mappati in memoria. In questo esempio, ho avuto solo un processo Java in esecuzione, così ho il sospetto che il 7k è il risultato di librerie utilizzate dal sistema operativo.
  • SWAP non è attivata per impostazione predefinita e non viene mostrato qui. Esso indica la quantità di memoria virtuale che è attualmente residente sul disco, anche se non è in realtà nello spazio di swap . Il sistema operativo è molto buono su come mantenere pagine attive in RAM, e le uniche cure per lo scambio sono (1) acquistare più memoria, o (2) ridurre il numero di processi, quindi è meglio per ignorare questo numero.

La situazione per Windows Task Manager è un po 'più complicato. In Windows XP, ci sono "Memory Usage" e "Virtual Memory Size" colonne, ma il documentazione ufficiale tace su quello che significano. Windows Vista e Windows 7 aggiungere più colonne, e sono in realtà documentato . Di questi, la misura "working set" è la più utile; corrisponde all'incirca alla somma delle FER e SHR su Linux.

Comprendere la memoria virtuale Mappa

La memoria virtuale consumata da un processo è il totale di tutto ciò che è nella mappa di memoria del processo. Questo comprende i dati (ad esempio, l'heap Java), ma anche tutte le librerie condivise ei file mappati in memoria utilizzati dal programma. Su Linux, è possibile utilizzare il comando pmap per vedere tutte le cose mappate nel processo spazio (da qui in avanti sto solo andando a fare riferimento a Linux, perché è quello che uso, sono sicuro che ci sono strumenti equivalenti per Windows). Ecco un estratto dalla mappa di memoria del programma "Ciao Mondo"; l'intera mappa della memoria è lunga più di 100 linee, e non è insolito avere un elenco mille-line.

0000000040000000     36K r-x--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000      8K rwx--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000    676K rwx--    [ anon ]
00000006fae00000  21248K rwx--    [ anon ]
00000006fc2c0000  62720K rwx--    [ anon ]
0000000700000000 699072K rwx--    [ anon ]
000000072aab0000 2097152K rwx--    [ anon ]
00000007aaab0000 349504K rwx--    [ anon ]
00000007c0000000 1048576K rwx--    [ anon ]
...
00007fa1ed00d000   1652K r-xs-  /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000   1024K rwx--    [ anon ]
00007fa1ed2d3000      4K -----    [ anon ]
00007fa1ed2d4000   1024K rwx--    [ anon ]
00007fa1ed3d4000      4K -----    [ anon ]
...
00007fa1f20d3000    164K r-x--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000   1020K -----  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000     28K rwx--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000     16K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000      4K rwx--  /lib/x86_64-linux-gnu/libc-2.13.so
...

Una rapida spiegazione del formato: ogni riga inizia con l'indirizzo di memoria virtuale del segmento. Questo è seguito dalla dimensione del segmento, i permessi, e la fonte del segmento. Questo ultimo articolo è un file o "anon", che indica un blocco di memoria allocata tramite mmap .

A partire dalla cima, abbiamo

  • Il caricatore JVM (vale a dire, il programma che viene eseguito quando si digita java). Questo è molto piccola; tutto ciò che fa è carico nelle librerie condivise in cui è memorizzato il codice vero e proprio JVM.
  • Un mucchio di blocchi anon in possesso di un heap Java e dati interni. Si tratta di una JVM Sun, in modo che il mucchio è bRoken in più generazioni, ciascuna delle quali è proprio blocco di memoria. Si noti che il JVM alloca spazio di memoria virtuale in base al valore -Xmx; questo permette di avere un mucchio contigua. Il valore -Xms viene utilizzato internamente per dire quanto del mucchio è "in uso" quando si avvia il programma, e per attivare la raccolta dei rifiuti come tale limite si avvicina.
  • Un JarFile, in questo caso il file mappato in memoria che contiene le "classi JDK." Quando la memoria-MAP un JAR, è possibile accedere ai file al suo interno in maniera molto efficiente (rispetto a leggerlo dall'inizio ogni volta). Il Sun JVM memoria a mappare tutti i JAR nel classpath; Se il codice applicazione ha bisogno di accedere ad un JAR, è possibile anche la memoria a mappare.
  • Per-filo dati per due fili. Il blocco 1M è una pila filo; Non so che cosa va in blocco di 4K. Per una vera e propria applicazione, si vedrà decine se non centinaia di queste voci ripetute attraverso la mappa della memoria.
  • Una delle librerie condivise che contiene il codice JVM vero e proprio. Ci sono molti di questi.
  • La libreria condivisa per la libreria C standard. Questa è solo una delle tante cose che i carichi JVM non strettamente parte del Java.

Le librerie condivise sono particolarmente interessanti: ogni libreria condivisa ha almeno due segmenti: un segmento di sola lettura che contiene il codice della libreria, e un segmento di lettura-scrittura che contiene i dati globali per-processo per la biblioteca (non lo faccio sa che il segmento con nessuna autorizzazione è: ho visto solo su x64 Linux). La porzione di sola lettura della libreria può essere condivisa tra tutti i processi che utilizzano la libreria; per esempio, ha libc 1.5M di spazio di memoria virtuale che può essere condivisa.

Quando è virtuale Dimensione memoria importante?

La mappa della memoria virtuale contiene un sacco di roba. Una parte di esso è di sola lettura, alcune delle quali è condivisa, e alcuni di essi è assegnato, ma mai toccato (ad esempio, quasi tutti i 4Gb di heap in questo esempio). Ma il sistema operativo è abbastanza intelligente per caricare solo ciò di cui ha bisogno, quindi la dimensione della memoria virtuale è in gran parte irrilevante.

Quando la dimensione della memoria virtuale è importante è se si sta eseguendo su un sistema a 32 bit di funzionamento, in cui è possibile allocare solo 2 GB (o, in alcuni casi, 3 Gb) di spazio di indirizzi del processo. In questo caso hai a che fare con una risorsa scarsa, e potrebbe essere necessario fare compromessi, come ad esempio ridurre le dimensioni heap al fine di memoria mappare un file di grandi dimensioni o di creare un sacco di discussioni.

Ma, dato che le macchine a 64 bit sono onnipresenti, non credo che passerà molto tempo prima virtuale Dimensione memoria è una statistica del tutto irrilevante.

Quando è Resident Set Size importante?

Resident Set dimensione è quella porzione di spazio di memoria virtuale che è in realtà in RAM. Se il vostro RSS cresce fino a essere una parte significativa della vostra memoria fisica totale, potrebbe essere il momento di cominciare a preoccuparsi. Se il vostro RSS cresce fino a occupare tutto la memoria fisica, e il sistema si avvia lo swapping, è tempo ben oltre a iniziare a preoccuparsi.

Ma RSS è anche fuorviante, soprattutto su una macchina leggermente caricato. Il sistema operativo non spendere un sacco di sforzo per rivendicare le pagine utilizzate da un processo. C'è poco vantaggio da guadagnare in questo modo, e la possibilità di un errore di pagina costoso se il processo tocca la pagina in futuro. Di conseguenza, il RSS statistica può includere un sacco di pagine che non sono in uso attivo.

Bottom Line

A meno che non si sta scambiando, non si ottiene eccessivamente preoccupati per quello che le varie statistiche della memoria che si stanno dicendo. Con l'avvertenza che un RSS sempre crescente potrebbe indicare una sorta di perdita di memoria.

Con un programma Java, è molto più importante prestare attenzione a ciò che sta accadendo nel mucchio. La quantità totale di spazio consumato è importante, e ci sono alcuni passaggi che si possono adottare per ridurre tale. Più importante è la quantità di tempo che si spende nella raccolta dei rifiuti, e quali parti del mucchio stanno ottenendo collected.

Accesso al disco (ad esempio, un database) è costoso, e la memoria è a buon mercato. Se è possibile barattare uno per l'altro, farlo.

Altri suggerimenti

C'è un problema noto con Java e glibc> = 2.10 (include Ubuntu> = 10.04, RHEL> = 6).

La cura è di impostare questo ENV. variabile:

export MALLOC_ARENA_MAX=4

Se si esegue Tomcat, è possibile aggiungere questo a TOMCAT_HOME/bin/setenv.sh file.

Per Docker, aggiungere questo Dockerfile

ENV MALLOC_ARENA_MAX=4

C'è un articolo di IBM sull'impostazione MALLOC_ARENA_MAX https://www.ibm.com/developerworks/ comunità / blogs / kevgrig / ingresso / linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage? lang = it

Questo post del blog dice

  

memoria residente è stato conosciuto per insinuarsi in modo simile a un   perdita di memoria o di frammentazione della memoria.

C'è anche un open JDK bug JDK-8.193.521 "rifiuti glibc memoria con configurazione di default "

ricerca di MALLOC_ARENA_MAX su Google o Così, per ulteriori riferimenti.

Si potrebbe desiderare di mettere a punto anche altre opzioni per ottimizzare malloc per la bassa frammentazione di memoria allocata:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

La quantità di memoria allocata per il processo di Java è più o meno in pari con quello che mi aspetterei. Ho avuto problemi simili in esecuzione Java su sistemi limitati embedded / memoria. Esecuzione di qualsiasi applicazione con i limiti di VM arbitrari o su sistemi che non dispongono di adeguate quantità di scambio tendono a rompersi. Sembra essere la natura di molte applicazioni moderne che non sono progettare per l'utilizzo su sistemi con risorse limitate.

Hai alcune opzioni in più si può provare e limitare l'occupazione di memoria della JVM. Questo potrebbe ridurre l'impronta di memoria virtuale:

  

XX: ReservedCodeCacheSize = 32m Reserved dimensione della cache codice (in bytes) - massimo   Codice dimensione della cache. [Solaris a 64 bit,   amd64, e -server x86: 48m; nel   1.5.0_06 e precedenti, Solaris a 64 bit e and64:. 1024m]

     

XX: MaxPermSize = 64m Dimensioni della Generazione permanente. [5.0 e successivi:   64 VM bit sono scalati maggiore del 30%; 1.4   amd64: 96m; 1.3.1 -client:. 32m]

Inoltre, è anche necessario impostare il vostro -Xmx (max dimensione heap) ad un valore il più vicino possibile alla di picco effettivo utilizzo della memoria della vostra applicazione. Credo che il comportamento predefinito della JVM è ancora da doppia la dimensione heap ogni volta che si espande fino al massimo. Se si inizia con 32M mucchio e la vostra applicazione ha raggiunto un picco a 65M, poi il mucchio finirebbe crescente 32M -> 64M -.> 128M

Si potrebbe anche provare questo per fare la VM meno aggressiva di crescere mucchio:

  

XX: MinHeapFreeRatio = 40 percentuale minima di memoria libera dopo GC   evitare di espansione.

Inoltre, da quello che mi ricordo da sperimentare con questo a pochi anni fa, il numero di librerie native caricata ha avuto un enorme impatto sul minimo ingombro. Caricamento java.net.Socket aggiunto più di 15m, se non ricordo male (e probabilmente non lo fanno).

Il Sun JVM richiede un sacco di memoria per HotSpot e mappe nel librerie di runtime nella memoria condivisa.

Se la memoria è un problema considerare l'utilizzo di un altro JVM adatto per l'incorporamento. IBM ha j9, e v'è la "jamvm" Open Source che utilizza le librerie GNU classpath. Anche Sun ha lo Squeak JVM in esecuzione sulle macchie solari quindi non ci sono alternative.

Solo un pensiero, ma si può verificare l'influenza di un'opzione ulimit -v .

Questa non è una soluzione reale in quanto limiterebbe lo spazio di indirizzi disponibili per tutti processo, ma che permetterebbe di verificare il comportamento dell'applicazione con una memoria virtuale limitata.

Un modo per ridurre la SICE mucchio di un sistema con risorse limitate potrebbe essere quella di giocare con il XX: variabili MaxHeapFreeRatio. Questo è normalmente impostato a 70, ed è la percentuale massima dell'heap che è libera prima del GC restringe. L'impostazione ad un valore più basso, e si vedrà nel esempio il profiler jvisualvm che un mucchio sice più piccola è di solito utilizzato per il programma.

EDIT: per impostare valori piccoli per -XX: MaxHeapFreeRatio è necessario impostare anche -XX: MinHeapFreeRatio Ad esempio,

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2: aggiunta un esempio di applicazione reale che avvia e fa lo stesso compito, uno con parametri di default e uno con 10 e 25 come parametri. Non ho notato alcuna differenza di velocità reale, anche se java in teoria dovrebbe utilizzare più tempo per aumentare il mucchio in quest'ultimo esempio.

Parametri di default

Alla fine, heap massima è 905, mucchio utilizzato è 378

MinHeap 10, MaxHeap 25

Alla fine, heap massima è 722, mucchio utilizzato è 378

Questo in realtà hanno una certa inpact, come la nostra applicazione gira su un server di desktop remoto, e molti utenti possono eseguire in una sola volta.

Java di Sun 1.4 ha i seguenti argomenti per controllare la dimensione della memoria:

  

-Xmsn       Specificare la dimensione iniziale, in byte, del pool di allocazione di memoria.   Questo valore deve essere un multiplo di 1024   maggiore di 1 MB. Aggiungere la lettera k   o K per indicare kilobyte, oppure mo M   per indicare megabyte. Il predefinito   valore è 2 MB. Esempi:

           -Xms6291456
           -Xms6144k
           -Xms6m
     

-Xmxn       Specificare la dimensione massima, in byte, del pool di allocazione di memoria.   Questo valore deve un multiplo di 1024   superiore a 2 MB. Aggiungere la lettera k   o K per indicare kilobyte, oppure mo M   per indicare megabyte. Il predefinito   valore è 64 MB. Esempi:

           -Xmx83886080
           -Xmx81920k
           -Xmx80m

http://java.sun.com /j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 e 6 hanno un po 'di più. Vedere http://java.sun.com/javase/technologies/hotspot/vmoptions .jsp

No, non è possibile configurare quantità di memoria necessaria per VM. Tuttavia, notare che questa è la memoria virtuale, non residente, in modo che solo rimane lì senza danno, se non effettivamente utilizzate.

Alernatively, si può provare qualche altro JVM allora Sun One, con il più piccolo ingombro di memoria, ma non posso consigliare qui.

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