使用 Criteria API Pageable 的内存泄漏
Posted
技术标签:
【中文标题】使用 Criteria API Pageable 的内存泄漏【英文标题】:Memory leak with Criteria API Pageable 【发布时间】:2021-08-30 10:00:39 【问题描述】:我在 Criteria API 查询中实现了可分页功能,并且我注意到在查询执行期间内存使用量增加了。我还使用 spring-data-jpa 方法查询来返回相同的结果,但是在处理完每个批次后都会清理内存。我尝试从 EntityManager 中分离、刷新、清除对象,但内存使用量会继续上升,偶尔会下降,但不如方法查询那么多。我的问题是,如果对象被分离,什么会导致这种内存使用以及如何处理它?
Criteria API 可分页的内存使用情况:
方法查询的内存使用情况:
代码
由于我也在更新从数据库中检索到的实体,因此我使用保存最后处理实体的 ID 的方法,因此当实体得到更新时,查询不会跳过下一个选定页面。下面我提供的代码示例并非来自我正在开发的真实应用程序,但它只是重现了我遇到的问题。
存储库代码:
@Override
public Slice<Player> getPlayers(int lastId, Pageable pageable)
List<Predicate> predicates = new ArrayList<>();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Player> criteriaQuery = criteriaBuilder.createQuery(Player.class);
Root<Player> root = criteriaQuery.from(Player.class);
predicates.add(criteriaBuilder.greaterThan(root.get("id"), lastId));
criteriaQuery.where(criteriaBuilder.and(predicates.toArray(Predicate[]::new)));
criteriaQuery.orderBy(criteriaBuilder.asc(root.get("id")));
var query = entityManager.createQuery(criteriaQuery);
if (pageable.isPaged())
int pageSize = pageable.getPageSize();
int offset = pageable.getPageNumber() > 0 ? pageable.getPageNumber() * pageSize : 0;
// Fetch additional element and skip it based on the pageSize to know hasNext value.
query.setMaxResults(pageSize + 1);
query.setFirstResult(offset);
var resultList = query.getResultList();
boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
else
return new SliceImpl<>(query.getResultList(), pageable, false);
遍历pageables:
@Override
public Slice<Player> getAllPlayersPageable()
int lastId = 0;
boolean hasNext = false;
Pageable pageable = PageRequest.of(0, 200);
do
var players = playerCriteriaRepository.getPlayers(lastId, pageable);
if(!players.isEmpty())
lastId = players.getContent().get(players.getContent().size() - 1).getId();
for(var player : players)
System.out.println(player.getFirstName());
entityManager.detach(player);
hasNext = players.hasNext();
while (hasNext);
return null;
【问题讨论】:
您能否附加一个配置文件并让它运行很长时间,以至于导致内存泄漏的原因占用了很大一部分内存?之后,配置文件应该能够指出填充该内存的对象以及阻止它们被 GC 的引用更改。这应该有助于确定根本原因。 在 52m 个实体上运行,它说主要嫌疑人是:由“jdk.internal.loader.ClassLoaders$AppClassLoader @ 0x68139d970”加载的“org.hibernate.internal.SessionFactoryImpl”实例占用 29,75 MB (60,38%)。尽管我无法将其追溯到该代码中的任何已知类。我在使用所有内存的树中注意到的最后一件事是:org.hibernate.internal.util.collections.BoundedConcurrentHashMap$Segment[32] @ 0x682175aa0 你能找到,BoundedConcurrentHashMap 中存储了什么吗? 【参考方案1】:我认为您在这里遇到了与使用 JPA Criteria API 以及如何处理数值有关的查询计划缓存问题。 Hibernate 会将所有数值作为文字呈现到中间 HQL 查询字符串中,然后编译该字符串。可以想象,每次“滚动”到下一页都会是一个新的查询字符串,因此您会逐渐填满查询计划缓存。
一种可能的解决方案是使用像Blaze-Persistence 这样的库,它具有自定义JPA Criteria API implementation 和Spring Data integration,这将避免这些问题,同时由于更好的分页实现而提高查询的性能。
您的所有代码都将保持不变,您只需要包含集成并按照setup section 中的说明对其进行配置。
【讨论】:
以上是关于使用 Criteria API Pageable 的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章