未使用的 JPA 实体是不是被垃圾回收?为啥?

Posted

技术标签:

【中文标题】未使用的 JPA 实体是不是被垃圾回收?为啥?【英文标题】:Are JPA entities that are not in use garbage collected and why?未使用的 JPA 实体是否被垃圾回收?为什么? 【发布时间】:2014-11-28 11:52:30 【问题描述】:

使用我多次碰到OutOfMemoryError: GC overhead limit exceeded 的 API 构建一个从 Web 获取数据的 Spring 应用程序。在一些分析会议之后,我开始质疑我的模型,这是这样的:

@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 实例吗?

我的另一个观察结果是,当数据库中没有数据时,该过程第一次运行得更加顺畅。

这个模型或这个过程有什么根本错误吗?

【问题讨论】:

【参考方案1】:

JPA 事务具有事务中使用的所有实体的关联会话缓存。通过保存您的实体,您可以不断将更多实例引入该会话缓存。在您的情况下,我建议每个 n 实体都使用 EntityManager.clear() - 这会将持久化的实体从会话中分离出来,并使它们可用于垃圾收集。

如果您想了解更多关于 JPA 实体的生命周期的信息,可以参考例如

http://www.objectdb.com/java/jpa/persistence/managed

编辑: 此外,BatScream 的答案也是正确的:您似乎在每次迭代中积累了越来越多的数据,这些数据仍然被集合引用。您可能需要考虑从集合中删除已处理的实例。

【讨论】:

啊,这是一个困难的情况:你的组合答案是完全正确的答案。【参考方案2】:

集合aCollection 在每次迭代后不断增长。在每个循环之后,A 的每个实例都将填充 200 个 B 实例条目。因此你的堆空间被吃光了。

当垃圾收集器在此期间运行时,集合aCollection 中的所有A 实例始终可访问,因为您不会从集合中删除刚刚保存的A

为避免这种情况,您可以使用 Set Iterator 从集合中安全地删除刚刚处理的 A 实例。

【讨论】:

以上是关于未使用的 JPA 实体是不是被垃圾回收?为啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 io类的资源,在使用完后,需要进行释放

JVM垃圾回收机制

JVM垃圾回收机制

三色标记法与垃圾回收器(CMS、G1)

垃圾回收CMS

Java垃圾回收算法