2010-10-18 16 views
14

Questo è uno stack di memoria (che funge da cache) costituito da nient'altro che una ConcurrentHashMap (CHM) statica.Memoria completamente utilizzata da Java ConcurrentHashMap (in Tomcat)

Tutti i dati delle richieste HTTP in arrivo sono archiviati in questa ConcurrentHashMap. E c'è un processo di scheduling asincrono che prende i dati dalla stessa ConcurrentHashMap e rimuove il valore key.value dopo averli memorizzati nel Database.

Questo sistema funziona bene e liscia, ma solo scoprire sotto seguenti criteri, la memoria è stata pienamente utilizzata (2,5 GB) e tutto il tempo della CPU è stata presa per eseguire GC:

-concurrent http hit del 1000/s

- mantiene lo stesso hit simultaneo per un periodo di 15 minuti

Il processo asynch registra la dimensione rimanente del CHM ogni volta che scrive nel database. Il CHM.size() mantiene intorno a Min: 300 a Max: 3500

Ho pensato che ci fosse una perdita di memoria su questa applicazione. quindi ho usato Eclipse MAT per dare un'occhiata alla discarica dell'heap. Dopo aver eseguito il Rapporto Suspect, ho ottenuto questi commenti da MAT:

Un esempio di "org.apache.catalina.session.StandardManager" caricati da "org.apache.catalina.loader.StandardClassLoader @ 0x853f0280" occupa 2.135.429,456 mila (94,76%) byte. La memoria viene accumulata in un'istanza di "java.util.concurrent.ConcurrentHashMap $ Segment []" caricato da "".

3,646,166 instances of java.util.concurrent.ConcurrentHashMap$Segment retain >= 2,135,429,456 bytes. 

e

Length # Objects  Shallow Heap  Retained Heap 
0   3,646,166  482,015,968  >= 2,135,429,456 

La lunghezza 0 sopra i tradurlo registrare lunghezza vuoto all'interno del CHM (ogni volta che chiamo metodo CHM.remove()). E 'coerente al numero di record di all'interno del database, 3,646,166 record era all'interno del database quando questa discarica è stato creato

Lo scenario strana è: se mi fermo lo stress test, l'utilizzo di memoria heap gradualmente sblocco verso il basso a 25 MB. Richiede circa 30-45 minuti. Ho ri-simulare questa applicazione e le curve sembra simile al VisualVM grafico qui sotto: alt text

Heres le domande:

1) Se questo si presenta come una perdita di memoria?

2) Ogni chiamata di rimozione remove(Object key, Object value) per rimuovere un <key:value> da CHM, quell'oggetto rimosso ottiene GC?

3) È qualcosa che ha a che fare con le impostazioni del GC? Ho aggiunto i seguenti parametri GC ma senza aiuto:

-XX:+UseParallelGC 

-XX:+UseParallelOldGC 

-XX:GCTimeRatio=19 

-XX:+PrintGCTimeStamps 

-XX:ParallelGCThreads=6 

-verbose:gc 

4) Qualsiasi idea per risolvere questo è molto apprezzata! :)

NUOVO 5) Potrebbe essere possibile perché tutti i miei riferimenti sono di riferimento? La mia comprensione è finchè la sessione HTTP è finita, tutte quelle variabili che non sono statiche sono ora disponibili per GC.

NEW Nota Ho provato a sostituire il CHM con ehcache 2.2.0, ma ottengo lo stesso problema di OutOfMemoryException. Suppongo che ehcache utilizzi anche ConcurrentHashMap.

Server Spec:

nucleo -Xeon Quad, 8 thread.

-4GB memoria

-Windows 2008 R2

-Tomcat 6.0.29

+0

Quanto sarebbe difficile sostituire la mappa hash con un'istanza di EhCache? Queste librerie sono ottimizzate per questo tipo di attività. –

+0

Al momento cerchiamo di non modificare molto il codice esistente perché dobbiamo ancora analizzare l'impatto. EhCache era inizialmente parte della considerazione, ma in qualche modo non era stata scelta come scelta di implementazione. – Reusable

risposta

2

1) Fa questo appare come una perdita di memoria?

Sì, se l'applicazione continua a mettere gli oggetti nella mappa e non li rimuove mai, potrebbe essere una perdita di memoria.

2) Ogni chiamata di rimozione rimossa (chiave Oggetto, valore oggetto) per rimuovere un da CHM, quell'oggetto rimosso ottiene GC?

Gli oggetti possono essere raccolti solo se non esiste un thread in esecuzione (in esecuzione) che abbia un riferimento ad essi. La mappa è solo un posto dove c'è un riferimento all'oggetto. Potrebbero ancora esserci altri luoghi che hanno riferimenti allo stesso oggetto. Ma mantenere l'oggetto nella mappa impedirà che vengano raccolti dei rifiuti.

3) È qualcosa che ha a che fare con le impostazioni del GC?

No; se un oggetto è referenziato, non può essere raccolto in modo improprio; non importa come si modifica il garbage collector.

+0

Seconda e terza domanda, devo concordare pienamente con te. Ma per quanto riguarda la prima domanda, se si tratta di una perdita di memoria, qualsiasi possibilità che dopo 30-45 minuti, l'utilizzo della memoria Heap Java ritorni allo stato di inizializzazione? – Reusable

+0

@Reusable se questo è ciò che accade, non dovrebbe essere etichettato come una vera "perdita di memoria". Tuttavia, se si scopre che la memoria non viene recuperata dopo aver pensato che dovrebbe essere (poiché non si fa più riferimento ai dati), ciò suggerisce che alcune logiche nell'utilizzo della mappa sono errate. –

+0

@matt b sto pensando allo stesso modo con te, ma è puramente la mia ipotesi approssimativa, che c'è qualche altro oggetto (s) che detiene il riferimento della chiave/valore che viene rimosso. Senza alcun successo e ancora controllando i codici. – Reusable

10

Questo problema mi ha infastidito per un brutto 7 giorni! E alla fine ho scoperto il vero problema! Di seguito sono riportati i compiti su ciò che ho provato ma non è riuscito a risolvere l'eccezione OutOfMemory:

-cambiare dall'utilizzo di concurrenthashmap a ehcache. (Risulta EHCache è anche utilizzando ConcurrentHashMap)

-change tutto il riferimento concreto a Soft Riferimento

-Override l'AbstractMap a fianco con concurrnetHashMap come da suggerire per Dr. Heinz M. Kabutz

La domanda da un milione di dollari è in realtà " perché 30-45 minuti dopo, la memoria inizia a tornare al pool di heap? "

L'effettiva causa principale era perché c'è qualcos'altro che ancora regge la sessione variabile effettiva, e il colpevole è la sessione http in tomcat è ancora attiva! Quindi, anche se la sessione http è stata completata, ma se l'impostazione di timeout è di 30 minuti, tomcat manterrà le informazioni sulla sessione per 30 minuti prima che JVM possa eseguire il GC. Problema risolto immediatamente dopo aver modificato l'impostazione del timeout a 1 minuto come test.

$tomcat_folder\conf\web.xml 

<session-config> 
    <session-timeout>1</session-timeout> 
</session-config> 

Spero che questo aiuti chiunque con problemi simili.

+0

Mi chiedo se passare a Gestione sessione persistente (utilizzando Archivio file) ha risolto il problema mantenendo il timeout della sessione? –

+0

Potrebbe. solo io ho il tempo di ripeterlo – Reusable

9

Penso che si sta utilizzando troppidati della sessione che non si adatta immediatamentein memoria. Provate questo:

  1. Modifica bin/setenv.sh o dovunque le args JVM sono impostati sul Tomcat launcher:

    Append -Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true

    esempio

    # Default Java options 
    if [ -z "$JAVA_OPTS" ]; then 
         JAVA_OPTS="-server -Djava.awt.headless=true -XX:MaxPermSize=384m -Xmx1024m -Dorg.apache.catalina.session.StandardSession.ACTIVITY_CHECK=true" 
    fi 
    
  2. Modifica conf/context.xml, prima di </Context> aggiungere questo:

    <Manager className="org.apache.catalina.session.PersistentManager" 
         maxIdleBackup="60" maxIdleSwap="300"> 
        <Store className="org.apache.catalina.session.FileStore"/> 
    </Manager> 
    

Riavviare Tomcat e il problema dovrebbe essere andato, dal momento che sarà negozio le sessioni che utilizzano il file system invece.

Nella mia impostazione session-timeout = 1 View è una soluzione che maschera la radice del problema, ed è inutilizzabile nella maggior parte delle applicazioni in cui hai veramente bisogno abbastanza session-timeout un grande. Le nostre app (Bippo) di solito hanno un session-timeout di 2880 minuti, vale a dire 2 giorni.

Riferimento: Tomcat 7.0 Session Manager Configuration

1

Naturalmente, è troppo tardi per rispondere, ma solo per le altre persone che troveranno questa domanda di ricerca. Potrebbe essere utile.

Questi 2 collegamenti sono molto utili
https://issues.apache.org/bugzilla/show_bug.cgi?id=50685
http://wiki.apache.org/tomcat/OutOfMemory

In breve, nella maggior parte dei casi si tratta di un software di test o il test sbagliato. Quando alcuni software personalizzati aprono l'URL, se questo software non è in grado di gestire la sessione http, tomcat crea una nuova sessione per ogni richiesta. Ad esempio è possibile controllarlo con un semplice codice, che può essere aggiunto a JSP.

System.out.println("session id: " + session.getId()); 
System.out.println("session obj: " + session); 
System.out.println("session getCreationTime: " + (new Date(session.getCreationTime())).toString()); 
System.out.println("session.getValueNames().length: " + session.getValueNames().length); 

Se ID sessione sarà lo stesso per un utente dal punto test di carico di vista, è bene, se ogni richiesta genera nuovo ID di sessione, che significa test del software non gestisce le sessioni molto bene e risultato del test non rappresenta il carico da utenti reali.

Per alcune applicazioni session.getValueNames(). Anche la lunghezza è importante, perché Ad esempio, quando l'utente normale lavora rimane lo stesso, ma quando il software di test di carico fa lo stesso, cresce. Significa anche che il software di test di carico non rappresenta molto bene il carico di lavoro reale. Nel mio caso session.getValueNames(). Length per utente normale era circa 100, ma qwith load test software dopo 10 minuti era circa 500 e infine il sistema si blocca con lo stesso errore OutOfMemory e MAT mostra lo stesso:

org. apache.catalina.loader.StandardClassLoader @ 0x853f0280 "occupa 2,135,429,456 (94,76%) byte.

0

Se si ottiene questa eccezione e si utilizza la versione iniziale di avvio 1.4.4 RELEASE o inferiore, impostare il valore della proprietà "server.session-timeout" in minuti, anziché su quello che suggeriscono (secondi), in modo che le sessioni su l'heap verrà pulito in tempo. Oppure è possibile utilizzare un bean di EmbeddedServletContainerCustomizer ma il valore fornito verrà impostato in minuti.

esempio (timeout della sessione in 10 minuti): server.session-timeout = 10 (impostate in Proprietà) container.setSessionTimeout (10, TimeUnit.SECONDS); (impostato in EmbeddedServletContainerCustomizer)

Problemi correlati