Questa è stata una lamentela di vecchia data con Java, ma è in gran parte insignificante e di solito basata sulla ricerca di informazioni sbagliate. Il solito fraseggio è qualcosa del tipo "Hello World su Java richiede 10 megabyte! Perché ne ha bisogno?" Bene, ecco un modo per rendere Hello World su una richiesta JVM a 64 bit di assumere oltre 4 gigabyte ... almeno con una forma di misurazione.
java -Xms1024m -Xmx4096m com.example.Hello
Diversi modi per misurare la memoria
Su Linux, il comando principale fornisce diversi numeri per la memoria. Ecco cosa dice dell'esempio Hello World:
PID UTENTE PR NI VIRT RES SHR S% CPU% MEM TIME + COMANDO
2120 kg registrati 20 0 4373m 15m 7152 S 0 0,2 0: 00.10 java
- VIRT è lo spazio di memoria virtuale: la somma di tutto nella mappa di memoria virtuale (vedi sotto). È in gran parte insignificante, tranne quando non lo è (vedi sotto).
- RES è la dimensione del set residente: il numero di pagine che sono attualmente residenti nella RAM. In quasi tutti i casi, questo è l'unico numero che dovresti usare quando dici "troppo grande". Ma non è ancora un numero molto buono, soprattutto quando si parla di Java.
- SHR è la quantità di memoria residente condivisa con altri processi. Per un processo Java, questo è in genere limitato alle librerie condivise e ai file JAR associati alla memoria. In questo esempio, avevo solo un processo Java in esecuzione, quindi sospetto che il 7k sia il risultato delle librerie utilizzate dal sistema operativo.
- SWAP non è attivato per impostazione predefinita e non è mostrato qui. Indica la quantità di memoria virtuale attualmente residente sul disco, indipendentemente dal fatto che sia effettivamente nello spazio di swap . Il sistema operativo è molto bravo a mantenere le pagine attive nella RAM e le uniche cure per lo scambio sono (1) acquistare più memoria o (2) ridurre il numero di processi, quindi è meglio ignorare questo numero.
La situazione per Task Manager di Windows è un po 'più complicata. In Windows XP, ci sono le colonne "Uso memoria" e "Dimensione memoria virtuale", ma la documentazione ufficiale non contiene informazioni sul loro significato. Windows Vista e Windows 7 aggiungono più colonne e sono effettivamente documentate . Di questi, la misura "Working Set" è la più utile; corrisponde approssimativamente alla somma di RES e SHR su Linux.
Comprensione della mappa della memoria virtuale
La memoria virtuale consumata da un processo è il totale di tutto ciò che è presente nella mappa della memoria di processo. Ciò include i dati (ad esempio, l'heap Java), ma anche tutte le librerie condivise e i file mappati in memoria utilizzati dal programma. Su Linux, puoi usare il comando pmap per vedere tutte le cose mappate nello spazio del processo (da qui in poi farò riferimento a Linux, perché è quello che uso; sono sicuro che ci sono strumenti equivalenti per Finestre). Ecco un estratto dalla mappa di memoria del programma "Hello World"; l'intera mappa di memoria è lunga oltre 100 righe e non è insolito avere un elenco di mille righe.
0000000040000000 36K rx-- /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 rx-- /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 rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /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. Segue la dimensione del segmento, le autorizzazioni e l'origine del segmento. Quest'ultimo elemento è un file o "anon", che indica un blocco di memoria allocato tramite mmap .
A partire dall'alto, abbiamo
- Il caricatore JVM (ovvero il programma che viene eseguito durante la digitazione
java
). Questo è molto piccolo; tutto ciò che fa è caricare nelle librerie condivise in cui è archiviato il codice JVM reale.
- Un gruppo di anon blocchi che contengono l'heap Java e i dati interni. Questa è una Sun JVM, quindi l'heap è suddiviso in più generazioni, ognuna delle quali è il proprio blocco di memoria. Si noti che JVM alloca lo spazio di memoria virtuale in base al
-Xmx
valore; questo gli consente di avere un heap contiguo. Il -Xms
valore viene utilizzato internamente per indicare quanta parte dell'heap è "in uso" all'avvio del programma e per attivare la garbage collection all'avvicinarsi di tale limite.
- Un file JAR mappato in memoria, in questo caso il file che contiene le "classi JDK". Quando si mappa in memoria un JAR, è possibile accedere ai file al suo interno in modo molto efficiente (anziché leggerlo ogni volta dall'inizio). Sun JVM mapperà in memoria tutti i JAR sul percorso di classe; se il codice dell'applicazione deve accedere a un JAR, è anche possibile mapparlo in memoria.
- Dati per thread per due thread. Il blocco 1M è lo stack di thread. Non avevo una buona spiegazione per il blocco 4k, ma @ericsoe lo ha identificato come un "blocco di protezione": non ha permessi di lettura / scrittura, quindi se si accede causerà un errore di segmento e la JVM lo rileva e lo traduce esso a
StackOverFlowError
. Per un'app reale, vedrai dozzine se non centinaia di queste voci ripetute attraverso la mappa di memoria.
- Una delle librerie condivise che contiene l'attuale codice JVM. Ce ne sono diversi.
- La libreria condivisa per la libreria standard C. Questa è solo una delle tante cose caricate da JVM che non fanno parte strettamente di Java.
Le librerie condivise sono particolarmente interessanti: ogni libreria condivisa ha almeno due segmenti: un segmento di sola lettura contenente il codice della libreria e un segmento di lettura / scrittura che contiene dati globali per processo per la libreria (non so quale sia il segmento senza permessi è; l'ho visto solo su Linux x64). La parte di sola lettura della libreria può essere condivisa tra tutti i processi che utilizzano la libreria; ad esempio, libc
ha 1,5 M di spazio di memoria virtuale che può essere condiviso.
Quando è importante la dimensione della memoria virtuale?
La mappa della memoria virtuale contiene molte cose. Alcuni di essi sono di sola lettura, alcuni sono condivisi e alcuni sono allocati ma mai toccati (ad esempio, quasi tutti i 4 GB di heap in questo esempio). Ma il sistema operativo è abbastanza intelligente da caricare solo ciò di cui ha bisogno, quindi la dimensione della memoria virtuale è in gran parte irrilevante.
La dimensione della memoria virtuale è importante se si esegue un sistema operativo a 32 bit, dove è possibile allocare solo 2 Gb (o, in alcuni casi, 3Gb) di spazio degli indirizzi di processo. In tal caso, hai a che fare con una risorsa scarsa e potresti dover fare dei compromessi, come ridurre le dimensioni dell'heap per mappare in memoria un file di grandi dimensioni o creare molti thread.
Ma, dato che le macchine a 64 bit sono onnipresenti, non credo che passerà molto tempo prima che la dimensione della memoria virtuale sia una statistica completamente irrilevante.
Quando è importante la dimensione del set di residenti?
La dimensione del set di residenti è quella parte dello spazio di memoria virtuale effettivamente presente nella RAM. Se il tuo RSS diventa una porzione significativa della tua memoria fisica totale, potrebbe essere il momento di iniziare a preoccuparti. Se il tuo RSS cresce per occupare tutta la tua memoria fisica e il tuo sistema inizia a scambiarsi, è molto tempo che inizi a preoccuparti.
Ma RSS è anche fuorviante, specialmente su una macchina leggermente caricata. Il sistema operativo non fa molti sforzi per recuperare le pagine utilizzate da un processo. In tal modo, si ottengono pochi vantaggi e il potenziale per un errore di pagina costoso se il processo tocca la pagina in futuro. Di conseguenza, la statistica RSS può includere molte pagine che non sono in uso attivo.
Linea di fondo
A meno che tu non stia scambiando, non preoccuparti eccessivamente di ciò che ti dicono le varie statistiche sulla memoria. Con l'avvertenza che un RSS in continua crescita può indicare una sorta di perdita di memoria.
Con un programma Java, è molto più importante prestare attenzione a ciò che sta accadendo nell'heap. La quantità totale di spazio consumato è importante e ci sono alcuni passaggi che è possibile adottare per ridurlo. Più importante è la quantità di tempo che passi nella raccolta dei rifiuti e quali parti dell'heap vengono raccolte.
L'accesso al disco (ovvero un database) è costoso e la memoria è economica. Se puoi scambiare l'uno con l'altro, fallo.