【问题标题】:Eclipselink - detached entities memory leakEclipselink - 分离实体内存泄漏
【发布时间】:2020-09-08 08:48:15
【问题描述】:

设置

我们目前在 JakartaEE 应用程序中使用 wildfly 和 eclipselink 作为 JPA 实现。应用程序本身是具有 REST、Service 和 DAO 层的 RESTful Web 服务器。 DAO 是唯一使用 EntityManager 的层。我们总是出于各种原因分离实体。

  • 防止 eclipselink 自动检查状态并将更改刷新到数据库
  • 防止 eclipselink 在多次读取时重用同一个对象 ...

但是,通过使用这种方法,我们注意到内存使用量激增,在某些情况下会导致 OutOfMemory 错误。

诊断

使用 VisualVM,我们发现了内存中存在大量实体实例的问题。

测试代码

这是我们遇到问题的代码示例(迁移一些历史数据)

LinkedList<SomeEntity> entities; //Here is loaded set of entities to process
while(!entities.isEmpty()) {
    SomeEntity entity = entities.removeFirst(); //We are iterating in quee fashion to allow GC to remove already processed items from memory
    if (entity.getItems().isEmpty()) {
        //this call is transactional
        entityService.delete(entity.getId());
     } else if (entity.getItems().stream().anyMatch(item -> item.getQuantity() > 0.0)){
        //DO SOME CHANGES ON ENTITY
        //this call is transactional
        entityService.update(operation);
     }
     entity = null;
}
entities = null;

观察

  • 在分析内存使用情况时,我们可以看到内存中实体类的数量不断增加。它与测试代码中使用的实体不同,但它是实体,大多数时候被其他对象引用。有时其中一部分被清除,但一段时间后总数会增加
  • 实例数大大超过数据库中的记录数
  • 这意味着每次在关系中引用对象时,都会创建新实例(这没关系)
  • 当我们创建堆转储并查看对象引用的位置时,只有 eclipselink 内部结构显示如下 relationshipSourceObject in org.eclipse.persistence.internal.indirection.UnitOfWorkQueryValueHolder#90312 owner in org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener#26713, ...)

我们尝试过的

这些都没有帮助:

  • 将 eclipselink.cache.type.default 设置为 WEAK、SOFT 甚至 NONE
  • 在 while 结束时手动调用 EntityManager.clear

在我的理解中,WEAK 应该足以防止 eclipselink 存储引用时间过长并防止 GC。但无论如何它都存储在某个地方,并且由于可以从 GC 根访问这些引用,因此它们被更新清除。任何人都可以解释这种行为或指出我的方向吗?

编辑

解决评论和克里斯的回答。有关我们如何使用 EM 和交易的更多信息。

我们正在使用 EntityManager.detach 方法进行分离,并且引用(@OneToMany@ManyToMany 等)应用了 Cascade.DETACH。加载必要的延迟加载引用是在分离之前完成的。

我同意关于重新获取实体的部分。我不介意在一段时间内在内存中拥有同一实体的多个实例。我的问题是为什么它没有被垃圾收集。

示例代码中的实体列表在后续数据库的一个事务中加载 UPDATE 或 DELETE(这也会将一些位提取到内存中创建更多实例)是每个实体的另一个事务。我可能希望在初始调用期间使用大部分堆,然后慢慢清除或保持大致相同。

关于使用 EntityManager

我们使用 wildfly 作为 JakartaEE 容器。默认情况下,它与 hibernate 作为 JPA 提供程序一起提供,但我们添加了 eclipselink 作为模块并在 persistence.xml 中配置了提供程序

根据documentation容器管理的EntityManager根据需要创建实例。

【问题讨论】:

  • 您是对所有内容使用单个 EntityManager,还是对每个进程使用新的 EntityManager?我敦促后者,作为具有延迟加载和更改跟踪的实体,保留对加载它们的 EM 的引用;遍历惰性关系可能会导致它加载到该 EntityManager 中,并且 JPA 要求 EntityManager 对其加载的所有内容(所有“托管”实例)具有硬引用,因此大多数弱/软/无缓存选项通常适用于共享缓存(在 EntityManagerFactory 级别)
  • 我正在使用使用@PersistenceContext 注入的容器管理器EntityManager。惰性关系应用了 Cascade.DETACH,因此分离父实体也应该分离引用的实例。
  • 分离并不意味着你认为它意味着什么。分离(以及级联分离)类似于选择性地清除 EntityManager,并且需要手动调用。实体(在 eclipseLink 中)仍然具有指向加载它的 EM 的链接,以允许获取未获取的惰性关系,这仍然会导致我在下面提到的问题,就像您调用 clear 一样。如果您看到 EclipseLink 对象持有您的实体 - 什么持有 EclipseLink 对象?某些东西必须持有基础 EM,否则它也将获得 GCd
  • 是否有任何选项可以在不分离实体的情况下禁用更改跟踪?这是我们这样做的主要原因。我认为容器持有 EM。我将编辑问题以添加更多详细信息。
  • 只有当应用程序对 EntityManager 的引用也是如此时,它才会被 GC。尽管托管实体和读取它们的 EntityManager 之间存在双向链接,但使用 EclipseLink,分离并不是您所想的那样。如果您正在缓存实体,最好从实体实例进行克隆,以确保没有 JPA 钩子用于缓存更改检测和延迟加载等内容。尝试'em.unwrap(JpaEntityManager.class).copy(entity, new CopyGroup());'而不是分离。

标签: java jakarta-ee memory-leaks garbage-collection eclipselink


【解决方案1】:

您是否正在缓存实体?清除不足以让您有效地缓存,好像那是您正在尝试的,可能与您当前的问题有关。从 EntityManager 加载的所有内容都是对该 EntityManager 的引用,因此我猜您正在读取大量已部分获取并缓存它们的实体,然后使用 EntityManager.clear() 尝试分离它们。

这些实体不再被“管理”,但仍然引用 EntityManager。一旦您获取某些内容,例如您在代码中显示的 entity.getItems() 调用,假设这是一个标准的 OneToMany,带有默认为延迟加载的反向指针,这将强制将所有“项目”提取到内存中.由于它们具有反向引用并且 EntityManager 未引用“this”实体,因此 Item 必须重新获取该实体。因此,您现在在内存 Entity1' -> Item1 -> Entity1 中有两个相同实体的实例。

这可以通过更复杂的对象图和重复的清除调用轻松构建。

这可以解决,但不能解决,但通过减少您在 EntityManager 中执行的范围来减少开销,以便可以将其重用于与该对象图相关的标识目的,并进行垃圾收集(并由 GC 清除)当它用于读取的对象也被 GC 清除时。

【讨论】:

  • 我编辑了问题以更好地解释我们如何使用 EM。
  • 如果您在 entityManager 中使用 CDI 和基本上 @ApplicationScoped bean 进行操作,您将如何缩小范围?
猜你喜欢
  • 2011-11-29
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-20
  • 2013-05-29
  • 1970-01-01
相关资源
最近更新 更多