2010-02-11 28 views
5

Necesito exportar una gran cantidad de datos de la base de datos. Aquí es clases que representa mis datos:OutOfMemory al leer grandes cantidades de datos usando Hibernate

public class Product{ 
... 

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

... }

ProductHtmlSource - contiene gran cadena dentro de la cual realmente se necesita para exportar.

Dado que el tamaño de los datos exportados es mayor que la memoria JVM, estoy leyendo mis datos por fragmentos. De esta manera:

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> 
    } 

}

Código de 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; 
    } 

El problema es que a pesar de que la limpieza de la sesión después de leer de cada fragmento de datos Product objetos acumula en alguna parte y estoy obtener la excepción OutOfMemory. El problema no está en el procesamiento del bloque de código, incluso sin él, me sale un error de memoria. El tamaño del lote tampoco es un problema ya que 1000 objetos se sientan fácilmente en la memoria.

Profiler mostró que los objetos se acumulan en la clase org.hibernate.engine.StatefulPersistenceContext.

El 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

Publicado stacktrace pero no creo que el ajuste de gc ayude. Intenté System.gc(); antes de leer la nueva memoria de lotes, la memoria aún se desborda. – Vladimir

Respuesta

4

Parece que está llamando a getProductIterator() con los números de fila iniciales y finales, mientras que getProductIterator() espera la fila inicial y el recuento de filas. A medida que su "límite superior" aumenta, está leyendo datos en trozos más grandes. Creo que quieres pasar batchSize como el segundo argumento para getProductIterator().

0

Se puede publicar el StackTrace Excepción? Se puede resolver pasando opciones de JVM adecuadas para GC.

Creo que esto está relacionado - Java StringBuilder huge overhead.

Parece de StackTrace que se está creando un String muy grande y está causando la excepción.

+0

¿Ha intentado almacenar en LOB y usar Streams para la salida? – Padmarag

1

A riesgo de parecer estúpido, ¿ha considerado hacer esto de otra manera?

Personalmente, evitaría hacer el procesamiento por lotes tan lejos de la base de datos. No sé qué base de datos está utilizando, pero generalmente hay un mecanismo para extraer eficientemente un conjunto de datos de la base de datos & en un archivo, incluso si implica una manipulación moderadamente simple en el camino de salida. Procedimientos almacenados, utilidades de exportación específicas. Investigue qué más está disponible de su proveedor de base de datos.

2

KeithL tiene razón: está pasando un límite cada vez mayor. Pero dividirlo de esa manera no tiene sentido de todos modos. El objetivo de un cursor de desplazamiento es procesar una fila a la vez, por lo que no es necesario dividirla en fragmentos. El tamaño de búsqueda reduce los viajes a la base de datos a costa de consumir más memoria.El patrón general debería ser:

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 
} 

Dicho esto, el error es getHtmlSources por lo que el problema puede no tener ninguna relación con la cuestión de sesión/cursor/desplazamiento. Si esas cadenas html son enormes y están siendo referenciadas todo el tiempo, es posible que se esté acabando la memoria contigua.

Por cierto, no veo un método getScrollableResults en ScrollableResults.

+0

"session.evict (p); // una alternativa a establecer el modo de caché arriba de" Esta afirmación es simplemente falsa, el modo de caché se refiere a L2 y el caché de consultas no a la sesión en sí. Una declaración de desalojo o declaración clara sigue siendo obligatoria. – Gab

+0

Gab es correcto. Actualicé la respuesta para reflejar eso. –

Cuestiones relacionadas