【发布时间】: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#90312owner 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