2012-03-05 4 views
6

Dove lavoro, si verificano problemi con la JVM che esaurisce lo spazio heap in una delle nostre applicazioni. Ho fatto un po 'di ricerca per la causa di questo, anche guardando un heap-dump con un profiler, ma ora sono praticamente bloccato.Hibernate, SessionFactoryObjectFactory e OutOfMemoryError: java heap space

In primo luogo, un po 'del sistema in questione: si tratta di un'applicazione Java, che utilizza Spring e Hibernate, per conservare record sulle organizzazioni. Il sistema è composto da un insieme di client Webservice che viene utilizzato per recuperare dati sulle organizzazioni dall'istituzione governativa responsabile di questo tipo di dati. Inoltre, il sistema mantiene un database locale con tali dati, agendo come una cache per le chiamate al servizio web, in modo che la prima volta che vengono richieste informazioni su un'organizzazione venga salvata in un database relazionale locale e utilizzata per il recupero di dati per le seguenti richieste. Hibernate è usato per comunicare con questo database.

Il problema, come indicato in precedenza, è che dopo un periodo di tempo, l'applicazione si avvia in modo anomalo con OutOfMemoryError: spazio java dell'heap. Ho esaminato una discarica dell'heap usando Eclipse + MAT e identificato il colpevole come SessionFactoryObjectFactory di Hibernate, occupando circa l'85% della memoria allocata (tutta conservata). Ho trovato un po 'difficile identificare esattamente quale tipo di oggetti sono contenuti in questo. Al livello più alto c'è Glassfish WebappClassLoader, che contiene org.hibernate.impl.SessionFactoryObjectFactory. Contenuto in questo è un org.hibernate.util.FastHashMap, che a sua volta contiene una java.util.HashMap. Questo contiene un numero di voci, ognuna delle quali contiene una voce HashMap, un org.hibernate.impl.SessionFactoryImpl e una stringa. La voce HashMap a sua volta contiene gli stessi tre oggetti, una voce HashMap, una SessionFactoryImpl e una String, e questa struttura si ripete un certo numero di volte. SessionFactoryImpls contiene un numero di oggetti, in particolare org.hibernate.persister.entity.SingleTableEntityPersister, che contiene un numero di stringhe e HashMaps. Alcune stringhe fanno riferimento a variabili negli oggetti dominio e alcune contengono istruzioni SQL.

Sembrava a prima vista che questo oggetto stesse occupando quantità di memoria non necessarie (il file di dump era 800 MB, di cui 650 MB erano occupati da SessionFactoryObjectFactory), quindi ho abilitato la registrazione del caricamento e dello scaricamento degli oggetti e ho provato a chiedere il sistema per i dati su un'organizzazione (tramite una chiamata webservice da un altro sistema). Quello che ho notato qui era che c'erano molti messaggi per il caricamento di oggetti, ma pochissimi sugli oggetti non caricati (gli unici che c'erano erano lo scarico di oggetti della libreria). Questo mi ha portato a credere che una volta che un oggetto (ad esempio un'organizzazione) è stato caricato in memoria, non viene mai scaricato, il che significa che nel tempo il sistema esaurirà la memoria. (Si tratta di una giusta ipotesi basata su ciò che è stato trovato nel registro?)

Poi, ho provato a trovare la causa di questo, ma questo era molto più difficile. Poiché gli oggetti caricati da Hibernate vivranno fino a quando la loro sessione di lavoro è attiva, ho provato ad alterare il modo in cui le sessioni sono state gestite, sostituendo le chiamate a Hibernate HinderSupport # getSession() di Spring, a HibernateDaoSupport # getSessionFactory(). GetCurrentSession(). Questo non ha avuto alcun effetto apparente sul problema. Ho anche provato ad aggiungere chiamate a ... getCurrentSession(). Flush() e .clear() nel blocco finally di alcuni dei metodi Dao in questione, anche senza alcun effetto apparente. (I metodi Dao sono tutti annotati con @Transactional, il che dovrebbe significare che una sessione dovrebbe essere viva solo nel metodo @ Transactional e chiamate consecutive al metodo dovrebbero ottenere sessioni diverse quando si chiama getCurrentSession() (?))

Quindi, ora sono praticamente bloccato quando si tratta di trovare altre aree da controllare. Qualcuno ha un'idea o qualche puntatore su dove guardare e cosa cercare?

Il dump dell'heap ha mostrato che ci sono molte istanze di org.hibernate.impl.SessionFactoryImpl, è come previsto? (Avrei pensato che ci dovrebbe essere solo un'istanza di SessionFactory, o alcuni top.)

Grazie in anticipo per eventuali risposte.

saluti,

több

Edit:

penso di aver effettivamente mananged per risolvere il problema:

Si è scoperto in questo modo le dipendenze ad altri oggetti sono stati gestiti nel il webservice-classes era il problema. Questo è stato risolto chiamando il nuovo ClassPathXmlApplicationContext (...) nel costruttore delle classi webservice. Ciò ha portato a caricare molti oggetti per ogni richiesta (o almeno ogni sessione), che non sono stati nuovamente scaricati (principalmente SessionFactoryImpl di Hibernate). Ho modificato le classi di servizi web in modo da iniettare la loro dipendenza e, al posto di ciò che ho visto usando i profiler, il problema con più oggetti SessionFactoryImpl è stato risolto.

Penso che il problema potrebbe essere stato aggravato dall'aggiornamento da GlassFish 2.x a GlassFish 3.x, potrebbero essere alcune differenze su come vengono istanziate le classi webservice.

risposta

5

potrei anche aggiungere la soluzione a questo problema in una risposta, invece di nella domanda stessa:

Il colpevole qui è stato come il caricamento di primavera in grano è stato fatto in vari oggetti, in particolare in webservice-classi. Questo è stato fatto chiamando

new ClassPathXmlApplicationContext(...)

Nelle singole classi di servizi web. Fare questo ha il cattivo effetto collaterale degli oggetti caricati evitando di essere spazzatura raccolta (immagino perché sono referenziati da alcuni interni di Spring). Sembra che un cambiamento nella versione glassfish abbia fatto qualcosa per l'istanziazione degli oggetti webservice, portando le chiamate a molte più chiamate a nuove, e quindi a molti più oggetti indesiderati che occupano memoria, fino a quando non si riempie e si blocca.

La soluzione al problema è stato quello di spostare le chiamate al

new ClassPathXmlApplicationContext(...)

fuori in una classe diversa, utilizzando la fabbrica-modello statico, qualcosa di simile:

public class ContextHolder { 
    private static ClassPathXmlApplicationContext context; 

    public static getSpringContext() { 
     if (context == null) { 
      context = new ClassPathXmlApplicationContext("applicationContext.xml"); 
     } 
     return context; 
    } 
} 

e chiamando questo le classi webservice, invece di new-class ClassPathXmlApplicationContext.

Aggiornamento:

ClassPathXmlApplicationContext è Closeable/Autocloseable, quindi try-with-resource un'altra possibilità:

try (final ClassPathXmlApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("applicationContext.xml")) { 
    //do stuff 
} 
+0

alternativa alla fabbrica statica, penso che il 'ClassPathXmlApplicationContext' ha un close'-metodo' che può essere chiamato per evitare questo problema. – Tobb