使用Hibernate阅读大量数据时OutofMemory
-
19-09-2019 - |
题
我需要从数据库中导出大量数据。这是代表我的数据的类:
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个对象很容易坐在内存中。
Profiler表明对象积累 org.hibernate.engine.StatefulPersistenceContext
班级。
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)**
解决方案
看来您正在调用getProductiterator()带有启动和结束的行号,而getProductiterator()期望开始行和行计数。随着您的“上限”变得更高,您正在读取更大块的数据。我认为您的意思是将批处理作为第二个参数将其传递给getProductuctiterator()。
其他提示
不是直接的答案,而是为了进行这种数据操纵,我会使用 无状态的界面.
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字符串很大,并且整个时间都被引用,那么您可能只用尽了连续的内存。
顺便说一句,我看不到卷轴上的getCrollableresults方法。
冒着看起来愚蠢的风险 - 您是否考虑过另一种方式?
我个人会避免进行批处理处理,从而远离数据库。我不知道您正在使用什么数据库,但是通常有一种机制可以有效地将数据集从数据库中提出并进入文件中,即使它涉及到出路时中度简单的操作。存储程序,特定的出口公用事业。调查数据库供应商可用的其他内容。
您可以发布异常堆栈Trace吗?可以通过通过合适的GC选项来解决它。
我认为这是相关的 - Java StringBuilder巨大的开销.
从堆栈Trace看,正在创建一个非常大的字符串并导致例外。