OutOfMemory при чтении больших объемов данных с использованием спящего режима - PullRequest
5 голосов
/ 11 февраля 2010

Мне нужно экспортировать большой объем данных из базы данных. Вот классы, которые представляют мои данные:

public class Product{
...

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

... }

ProductHtmlSource - содержит большую строку, внутри которой мне действительно нужно экспортировать.

Поскольку размер экспортируемых данных больше, чем объем памяти JVM, я читаю свои данные кусками. Как это:

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

}

Код 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;
    }

Проблема в том, что, несмотря на то, что я очищаю сессию после чтения каждого фрагмента данных, Product объекты где-то накапливаются, и я получаю исключение OutOfMemory. Проблема не в блоке обработки кода, даже без него я получаю ошибку памяти. Размер пакета также не является проблемой, поскольку 1000 объектов легко помещаются в память.

Профилировщик показал, что объекты накапливаются в классе org.hibernate.engine.StatefulPersistenceContext.

Трассировка стека:

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)**

Ответы [ 5 ]

4 голосов
/ 11 февраля 2010

Похоже, что вы вызываете getProductIterator () с начальным и конечным номерами строк, а getProductIterator () ожидает начальную строку и количество строк. Когда ваш «верхний предел» становится выше, вы читаете данные большими блоками. Я думаю, что вы хотите передать batchSize в качестве второго аргумента getProductIterator ().

2 голосов
/ 11 февраля 2010

KeithL прав - вы превышаете постоянно растущий лимит. Но разбивать его таким образом не имеет смысла. Весь смысл курсора прокрутки в том, что вы обрабатываете строку за раз, поэтому нет необходимости разбивать ее на куски. Размер выборки уменьшает количество обращений к базе данных за счет использования большего количества памяти. Общий шаблон должен быть:

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
}

Тем не менее, ошибка - getHtmlSources, поэтому проблема может быть совершенно не связана с проблемой сессии / курсора / прокрутки. Если эти строки html огромны и на них все время ссылаются, возможно, вам просто не хватает непрерывной памяти.

Кстати, я не вижу метода getScrollableResults в ScrollableResults.

2 голосов
/ 11 февраля 2010

Не прямой ответ, но для такого рода манипулирования данными я бы использовал интерфейс StatelessSession .

1 голос
/ 11 февраля 2010

С риском оказаться глупым - вы думали сделать это по-другому?

Лично я бы не стал выполнять пакетную обработку "далеко" от базы данных. Я не знаю, какую базу данных вы используете, но обычно есть механизм для эффективного извлечения набора данных из базы данных и в файл, даже если это требует умеренно простых манипуляций на выходе. Хранимые процедуры, конкретные экспортные утилиты. Узнайте, что еще доступно у поставщика вашей базы данных.

0 голосов
/ 11 февраля 2010

Можете ли вы опубликовать трассировку стека исключений? Это может быть решено путем передачи подходящих опций JVM для GC.

Я думаю, что это связано - Java StringBuilder огромные накладные расходы .

Из StackTrace выглядит, что создается очень большая строка и вызывается исключение.

...