2010-02-11 12 views
5

Ho bisogno di esportare grandi quantità di dati dal database. Ecco le classi che rappresenta i miei dati:OutOfMemory durante la lettura di grandi quantità di dati utilizzando la sospensione

public class Product{ 
... 

    @OneToMany 
    @JoinColumn(name = "product_id") 
    @Cascade({SAVE_UPDATE, DELETE_ORPHAN}) 
    List<ProductHtmlSource> htmlSources = new ArrayList<ProductHtmlSource>(); 

...}

ProductHtmlSource - contiene il Big stringa all'interno del quale ho effettivamente bisogno di esportare.

Dal dimensione dei dati esportati è più grande di memoria JVM che sto leggendo i miei dati per blocchi. Come questo:

final int batchSize = 1000;  
for (int i = 0; i < 50; i++) { 
    ScrollableResults iterator = getProductIterator(batchSize * i, batchSize * (i + 1)); 
    while (iterator.getScrollableResults().next()) { 
    Product product = (Product) iterator.getScrollableResults().get(0); 
    List<String> htmls = product.getHtmlSources(); 
    <some processing> 
    } 

}

Codice di getProductIterator:

public ScrollableResults getProductIterator(int offset, int limit) { 
     Session session = getSession(true); 
     session.setCacheMode(CacheMode.IGNORE); 
     ScrollableResults iterator = session 
       .createCriteria(Product.class) 
       .add(Restrictions.eq("status", Product.Status.DONE)) 
       .setFirstResult(offset) 
       .setMaxResults(limit) 
       .scroll(ScrollMode.FORWARD_ONLY); 
     session.flush(); 
     session.clear(); 

     return iterator; 
    } 

Il problema è che, nonostante io compensazione sessione dopo aver letto di ciascun blocco di dati Product oggetti si accumulano da qualche parte e io sono ottieni l'eccezione OutOfMemory. Il problema non è nell'elaborare il blocco del codice anche senza di esso ottengo un errore di memoria. Anche la dimensione del batch non è un problema dato che 1000 oggetti si possono facilmente mettere in memoria.

Profiler ha mostrato che gli oggetti si accumula nel org.hibernate.engine.StatefulPersistenceContext classe.

Lo stacktrace:

Caused by: java.lang.OutOfMemoryError: Java heap space 
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:99) 
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:518) 
    at java.lang.StringBuffer.append(StringBuffer.java:307) 
    at org.hibernate.type.TextType.get(TextType.java:41) 
    at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163) 
    at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:154) 
    at org.hibernate.type.AbstractType.hydrate(AbstractType.java:81) 
    at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2101) 
    at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1380) 
    at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1308) 
    at org.hibernate.loader.Loader.getRow(Loader.java:1206) 
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:580) 
    at org.hibernate.loader.Loader.doQuery(Loader.java:701) 
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236) 
    at org.hibernate.loader.Loader.loadCollection(Loader.java:1994) 
    at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:36) 
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:565) 
    at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:63) 
    at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1716) 
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:344) 
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86) 
    at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:109) 
    at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225) 
    **at com.rivalwatch.plum.model.Product.getHtmlSource(Product.java:76) 
    at com.rivalwatch.plum.model.Product.getHtmlSourceText(Product.java:80) 
    at com.rivalwatch.plum.readers.AbstractDataReader.getData(AbstractDataReader.java:64)** 
+0

Stacktrace pubblicato ma non credo che l'ottimizzazione di gc possa essere d'aiuto. Ho provato System.gc(); prima di leggere il nuovo batch, la memoria continua a traboccare. – Vladimir

risposta

4

Sembra che tu stia chiamando getProductIterator() con i numeri di riga iniziale e finale, mentre getProductIterator() si aspetta la riga iniziale e il numero di righe. Man mano che il "limite superiore" aumenta, stai leggendo i dati in blocchi più grandi. Penso che intendiate passare a batchSize come secondo argomento per ottenereProductIterator().

0

potete inserire lo stacktrace eccezione? Può essere risolto passando le opzioni JVM appropriate per GC.

credo che questo sia correlato - Java StringBuilder huge overhead.

guarda dal StackTrace che una stringa di grandi dimensioni è stato creato e causando l'eccezione.

+0

Hai provato a memorizzare in LOB e utilizzare gli stream per l'output? – Padmarag

1

A rischio di sembrare stupido, hai pensato di farlo in un altro modo?

Personalmente vorrei evitare di fare l'elaborazione batch che "lontano" dal database. Non so quale database si sta utilizzando, ma di solito c'è un meccanismo per estrarre in modo efficiente un set di dati dal database & in un file anche se comporta una manipolazione moderatamente semplice durante l'uscita. Procedure memorizzate, utilità di esportazione specifiche. Indagare cos'altro è disponibile dal proprio fornitore di database.

2

KeithL ha ragione: stai superando un limite sempre crescente. Ma sminuirlo in quel modo non ha comunque senso. L'intero punto di un cursore di scorrimento è che si elabora una riga alla volta, quindi non è necessario suddividerla in blocchi. La dimensione del recupero riduce i viaggi nel database al costo di utilizzare più memoria.Lo schema generale dovrebbe essere:

Query q = session.createCriteria(... no offset or limit ...); 
q.setCacheMode(CacheMode.IGNORE); // prevent query or second level caching 
q.setFetchSize(1000); // experiment with this to optimize performance vs. memory 
ScrollableResults iterator = query.scroll(ScrollMode.FORWARD_ONLY); 
while (iterator.next()) { 
    Product p = (Product)iterator.get(); 
    ... 
    session.evict(p); // required to keep objects from accumulating in the session 
} 

Detto questo, l'errore è getHtmlSources in modo che il problema può essere completamente estranei alla questione della sessione/cursore/scorrimento. Se quelle stringhe html sono enormi e vengono referenziate per tutto il tempo, potresti essere a corto di memoria contigua.

Btw, non vedo un metodo getScrollableResults su ScrollableResults.

+0

"session.evict (p); // un'alternativa all'impostazione della modalità cache sopra" Questa istruzione è solo falsa, la modalità cache riguarda L2 e la cache della query non la sessione stessa. Una dichiarazione di sfratto o chiara è ancora obbligatoria. – Gab

+0

Gab è corretto. Ho aggiornato la risposta per riflettere questo. –

Problemi correlati