构建一个使用API从Web获取数据的Spring应用程序,我多次碰到OutOfMemoryError: GC overhead limit exceeded
。在一些分析会议之后,我开始质疑我的模型,这是这样的:
@Entity
class A {
@Id
private Integer id;
private String name;
@OneToMany
private Set<B> b1;
@OneToMany
private Set<B> b2;
}
@Entity
Class B {
@Id
private Integer id;
@ManyToOne
private A a1;
@ManyToOne
private A a2;
}
分配了一个CrudRepository来管理这些实体(JPA + EclipseLink)。实体加载是默认的,在这种情况下意味着急切的AFAIK。
该程序尝试执行以下操作:
// populates the set with 2500 A instances.
Set<A> aCollection = fetchAFromWebAPI();
for (A a : aCollection) {
// populates b1 and b2 of each A with a 100 of B instances
fetchBFromWebAPI(a);
aRepository.save(a);
}
在此过程结束时,将有500k个B实例,但由于OutOfMemoryError: GC overhead limit exceeded
它永远不会到达终点。现在我可以添加更多内存,但我想了解为什么所有这些实例都不是垃圾回收?将A保存到数据库并忘记它。这是因为A实例在其b1或b2中有B实例,而这些实例又引用A实例吗?
我做的另一个观察是,当数据库中没有数据时,该过程第一次运行得非常顺畅。
此模型或此流程是否存在根本性问题?
答案 0 :(得分:3)
JPA事务具有事务中使用的所有实体的关联会话缓存。通过保存实体,您可以在该会话缓存中引入更多实例。在您的情况下,我建议使用EntityManager.clear()
每个n
实体 - 将持久化实体与会话分离,并使其可用于垃圾回收。
如果您想了解更多有关JPA实体生命周期的信息,可以参考例如
http://www.objectdb.com/java/jpa/persistence/managed
编辑: 此外,BatScream的答案也是正确的:您似乎在每个仍然被该集引用的迭代中累积越来越多的数据。您可能需要考虑从集合中删除已处理的实例。
答案 1 :(得分:2)
每次迭代后,集合aCollection
都会继续增长。每个A
实例将在每个循环后填充200个B
实例条目。因此你的堆空间被吃掉了。
在此期间垃圾收集器运行时,集合A
中的所有aCollection
实例始终可以访问,因为您没有从集合中删除刚刚保存的A
。
为避免这种情况,您可以使用Set Iterator
安全地从集合中删除刚处理过的A
实例。