Perché Sun JVM continua a consumare ancora più memoria RSS anche quando le dimensioni dell'heap, ecc. Sono stabili?

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

Domanda

Nell'ultimo anno ho apportato enormi miglioramenti all'utilizzo dell'heap Java della mia applicazione, con una solida riduzione del 66%. A tal fine, ho monitorato varie metriche, come le dimensioni dell'heap Java, CPU, Java non heap, ecc. Tramite SNMP.

Di recente, ho monitorato quanta memoria reale (RSS, set di residenti) dalla JVM e sono un po 'sorpreso. La vera memoria consumata da JVM sembra totalmente indipendente dalle dimensioni dell'heap delle mie applicazioni, non-heap, spazio eden, numero di thread, ecc.

Dimensione heap misurata da SNMP Java Grafico usato Heap Java http://lanai.dietpizza.ch/images/jvm- heap-used.png

Memoria reale in KB. (Es .: 1 MB di KB = 1 GB) Grafico dell'heap Java usato http://lanai.dietpizza.ch/images/jvm-rss. png

(Le tre immersioni nel grafico dell'heap corrispondono agli aggiornamenti / riavvii dell'applicazione)

Questo è un problema per me perché tutta quella memoria aggiuntiva che la JVM sta consumando sta 'rubando' memoria che potrebbe essere utilizzata dal sistema operativo per la memorizzazione dei file nella cache. In effetti, una volta che il valore RSS raggiunge ~ 2,5-3 GB, comincio a vedere tempi di risposta più lenti e un maggiore utilizzo della CPU dalla mia applicazione, principalmente per attendere IO. Quando parte il paging della partizione di swap, questo è molto indesiderabile.

Quindi, le mie domande:

  • Perché sta succedendo questo? Cosa sta succedendo & Quot; sotto il cofano & Quot; ?
  • Cosa posso fare per tenere sotto controllo il consumo reale di memoria della JVM?

I dettagli cruenti:

  • RHEL4 64-bit (Linux - 2.6.9-78.0.5.ELsmp # 1 SMP mer 24 set 2008 ... x86_64 ... GNU / Linux)
  • Java 6 (build 1.6.0_07-b06)
  • Tomcat 6
  • Applicazione (streaming video HTTP su richiesta)
    • I / O elevato tramite java.nio FileChannels
    • Da centinaia a basse migliaia di thread
    • Basso utilizzo del database
    • Primavera, letargo

Parametri JVM rilevanti:

-Xms128m  
-Xmx640m  
-XX:+UseConcMarkSweepGC  
-XX:+AlwaysActAsServerClassMachine  
-XX:+CMSIncrementalMode    

-XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps  
-XX:+PrintGCApplicationStoppedTime  
-XX:+CMSLoopWarn  
-XX:+HeapDumpOnOutOfMemoryError 

Come misuro RSS:

ps x -o command,rss | grep java | grep latest | cut -b 17-

Questo va in un file di testo e viene letto in un database RRD dal mio sistema di monitoraggio ad intervalli regolari. Nota che ps genera Kilo Byte.


The Problem & amp; Soluzione s :

Mentre alla fine era la ATorras che alla fine si è dimostrata corretta, kdgregory che mi ha guidato al percorso diagnostico corretto con l'uso di pmap. (Vai a votare entrambe le risposte!) Ecco cosa stava succedendo:

Cose che so per certo:

  1. La mia applicazione registra e visualizza i dati con JRobin 1.4 , qualcosa che ho codificato nella mia app più di tre anni fa .
  2. L'istanza più occupata dell'applicazione attualmente viene creata
    1. Oltre 1000 nuovi file di database JRobin (a circa 1,3 MB ciascuno) entro un'ora dall'avvio
    2. ~ 100 + ogni giorno dopo l'avvio
  3. L'app aggiorna questi oggetti della base di dati JRobin ogni 15 secondi, se c'è qualcosa da scrivere.
  4. Nella configurazione predefinita JRobin:
    1. utilizza un back-end di accesso ai file basato su java.nio. Questo back-end mappa MappedByteBuffers sui file stessi.
    2. una volta ogni cinque minuti un thread demone JRobin chiama MappedByteBuffer.force() su ogni MBB del database sottostante JRobin
  5. <=> elencato:
    1. 6500 mappature
    2. 5500 dei quali erano 1,3 MB di file di database JRobin, che funzionano a ~ 7,1 GB

L'ultimo punto è stato il mio momento &; Eureka! " .

Le mie azioni correttive:

  1. Prendi in considerazione l'aggiornamento all'ultimo JRobinLite 1.5.2 che è apparentemente migliore
  2. Implementare una corretta gestione delle risorse sui database JRobin. Al momento, una volta che la mia applicazione crea un database e non lo scarica mai dopo che il database non è più utilizzato attivamente.
  3. Prova a spostare <=> negli eventi di aggiornamento del database e non a un timer periodico. Il problema si risolverà magicamente?
  4. Immediatamente , cambia il back-end JRobin nell'implementazione java.io - un cambio di linea. Questo sarà più lento, ma forse non è un problema. Ecco un grafico che mostra l'impatto immediato di questo cambiamento.

Grafico della memoria RSS Java utilizzato http: //lanai.dietpizza. ch / images / StackOverflow-rss-problema-fixed.png

Domande che potrei o meno avere il tempo di capire:

  • Cosa sta succedendo all'interno della JVM con <=>? Se non è cambiato nulla, continua a scrivere l'intero file? Parte del file? Lo carica prima?
  • Esiste sempre una certa quantità di MBB sempre in RSS? (RSS era circa la metà delle dimensioni totali MBB allocate. Coincidenza? Sospetto di no.)
  • Se sposto <=> negli eventi di aggiornamento del database e non in un timer periodico, il problema andrà via magicamente?
  • Perché la pendenza RSS era così regolare? Non è correlato a nessuna delle metriche di caricamento dell'applicazione.
È stato utile?

Soluzione

Solo un'idea: i buffer NIO sono collocati all'esterno della JVM.

Modifica Come nel 2016 vale la pena considerare il commento di @Lari Hotari [ Perché Sun JVM continua a consumare ancora più memoria RSS anche quando le dimensioni dell'heap, ecc. sono stabili? ] perché fino al 2009, RHEL4 aveva glibc lt; 2,10 (~ 2,3)

Saluti.

Altri suggerimenti

RSS rappresenta le pagine che sono attivamente utilizzate: per Java, sono principalmente gli oggetti live nell'heap e le strutture di dati interne nella JVM. Non c'è molto che puoi fare per ridurne le dimensioni se non usare meno oggetti o eseguire meno elaborazioni.

Nel tuo caso, non penso che sia un problema. Il grafico sembra mostrare 3 meg consumati, non 3 gig mentre scrivi nel testo. È davvero piccolo ed è improbabile che stia causando il paging.

Quindi cos'altro sta succedendo nel tuo sistema? È una situazione in cui hai molti server Tomcat, ognuno dei quali consuma 3M di RSS? Stai lanciando un sacco di bandiere GC, indicano che il processo sta trascorrendo la maggior parte del suo tempo in GC? Hai un database in esecuzione sullo stesso computer?

Modifica in risposta ai commenti

Per quanto riguarda la dimensione RSS 3M - sì, sembrava troppo basso per un processo Tomcat (ho spuntato la mia casella e ne ho uno a 89M che non è attivo da un po '). Tuttavia, non mi aspetto necessariamente che sia & Gt; dimensione dell'heap e certamente non mi aspetto che sia quasi 5 volte la dimensione dell'heap (usi -Xmx640) - nel peggiore dei casi dovrebbe essere la dimensione dell'heap + una costante per app.

Il che mi fa sospettare i tuoi numeri. Quindi, anziché un grafico nel tempo, esegui quanto segue per ottenere uno snapshot (sostituisci 7429 con qualsiasi ID processo che stai utilizzando):

ps -p 7429 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize

(Modifica di Stu in modo da poter formattare i risultati alla richiesta di cui sopra per le informazioni ps :)

[stu@server ~]$ ps -p 12720 -o pcpu,cutime,cstime,cmin_flt,cmaj_flt,rss,size,vsize
%CPU - - - -  RSS SZ  VSZ
28.8 - - - - 3262316 1333832 8725584

Modifica per spiegare questi numeri per i posteri

RSS, come notato, è la dimensione del set residente: le pagine nella memoria fisica. SZ detiene il numero di pagine scrivibili dal processo (la commissione di commit); la manpage descrive questo valore come " molto approssimativo " ;. VSZ contiene le dimensioni della mappa di memoria virtuale per il processo: pagine scrivibili più pagine condivise.

Normalmente, VSZ è leggermente > SZ, e molto & Gt; RSS. Questo output indica una situazione molto insolita.

Elaborazione del motivo per cui l'unica soluzione è ridurre gli oggetti

RSS rappresenta il numero di pagine residenti nella RAM: le pagine a cui si accede attivamente. Con Java, il Garbage Collector percorre periodicamente l'intero grafico degli oggetti. Se questo grafico a oggetti occupa la maggior parte dello spazio dell'heap, il raccoglitore toccherà ogni pagina dell'heap, richiedendo che tutte quelle pagine diventino residenti in memoria. GC è molto bravo a compattare l'heap dopo ogni raccolta principale, quindi se stai eseguendo un heap parziale, la maggior parte delle pagine non dovrebbe essere nella RAM.

E alcune altre opzioni

Ho notato che hai menzionato di avere centinaia o migliaia di thread bassi. Le pile per questi thread si aggiungeranno anche all'RSS, anche se non dovrebbe essere molto. Supponendo che i thread abbiano una profondità di chiamata ridotta (tipica per i thread dei gestori del server delle app), ognuno dovrebbe consumare solo una o due pagine di memoria fisica, anche se c'è un costo di commit di mezzo mega per ciascuno.

  

Perché sta succedendo questo? Cosa sta succedendo & Quot; sotto il cofano & Quot ;?

JVM utilizza più memoria di un semplice heap. Ad esempio metodi Java, stack di thread e handle nativi vengono allocati in memoria separatamente dall'heap, nonché dalle strutture di dati interne JVM.

Nel tuo caso, le possibili cause dei problemi possono essere: NIO (già menzionato), JNI (già menzionato), creazione di thread eccessivi.

A proposito di JNI, hai scritto che l'applicazione non utilizzava JNI ma ... Che tipo di driver JDBC stai usando? Potrebbe essere un tipo 2 e perdere? È molto improbabile, tuttavia, come hai detto, l'utilizzo del database era basso.

Sulla creazione di thread eccessivi, ogni thread ottiene il proprio stack che può essere piuttosto grande. Le dimensioni dello stack dipendono effettivamente dalla VM, dal sistema operativo e dall'architettura, ad es. per JRockit è 256K su Linux x64, Non ho trovato il riferimento nella documentazione di Sun per la VM di Sun. Ciò influisce direttamente sulla memoria del thread (memoria del thread = dimensione dello stack del thread * numero di thread). E se crei e distruggi molti thread, probabilmente la memoria non viene riutilizzata.

  

Cosa posso fare per tenere sotto controllo il consumo reale di memoria della JVM?

Ad essere sincero, centinaia o migliaia di thread mi sembrano enormi. Detto questo, se hai davvero bisogno di così tanti thread, la dimensione dello stack dei thread può essere configurata tramite l'opzione -Xss. Ciò può ridurre il consumo di memoria. Ma non penso che questo risolverà l'intero problema. Tendo a pensare che ci sia una perdita da qualche parte quando guardo il grafico della memoria reale.

L'attuale garbage collector in Java è noto per non rilasciare memoria allocata, sebbene la memoria non sia più necessaria. È abbastanza strano, tuttavia, che la dimensione del tuo RSS aumenti a & GT; 3 GB sebbene la dimensione del tuo heap sia limitata a 640 MB. Stai utilizzando un codice nativo nella tua applicazione o hai il pacchetto di ottimizzazione delle prestazioni nativo per Tomcat abilitato? In tal caso, potresti ovviamente avere una perdita di memoria nativa nel tuo codice o in Tomcat.

Con Java 6u14, Sun ha introdotto il nuovo " Garbage-First " garbage collector, che è in grado di rilasciare memoria nel sistema operativo se non è più necessario. È ancora classificato come sperimentale e non abilitato per impostazione predefinita, ma se è un'opzione fattibile per te, proverei ad aggiornare alla versione più recente di Java 6 e abilitare il nuovo Garbage Collector con gli argomenti della riga di comando & Quot; -XX : + UnlockExperimentalVMOptions -XX: + UsaG1GC & Quot ;. Potrebbe risolvere il tuo problema.

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