Hibernate QueryPlanCache 查询计划缓存引发的内存溢出

目录

前言:在生产环境中有一个后端程序多次报oom然后导致程序中断。

1.排查方式

通过下载后端程序产生的oom文件,将oom文件导入MemoryAnalyzer程序分析程序堆内存使用情况。

1、将oom文件导入MemoryAnalyzer后可以看到概览信息界面。总共堆空间是5.6G,【org.hibernate.internal.SessionFactoryImpl @ 0x6c9877ab8】 这个对象占用 128 B 回收后可释放 4.8 GB,接下来看一下这个里面有哪些对象

2、第二步我们点开泄漏疑点看下分析报告

3、通过分析报告看到 org.hibernate.internal.SessionFactoryImpl 这个对象引用占用了总内存的 85.53%,总共引用大小是 5,116,381,640 字节,也就是 4.8G的大小。可以看到他引用了 QueryPlanCache 这个对象。

4、点开左上角第二个图标对象创建直方图,这里可以看到每个类有多少个实例,以及占用的内存。

可以看到这个char占用了很多内存,可以右键 List objects → with incoming references,就可以列出所有的char[]实例,以及每个char[]的整个引用关系链

通过关系链可以看到char实例都是存储的一些sql语句,点开第一个发现最后被HQLQueryPlan查询计划类引用了

5、点开左上角第三个图标打开整个堆的支配树,可以看到第一个实例占比达到了 85.53%。

我们点开这个占比 85.53% 的对象发现都是被 org.hibernate.internal.util.collections.BoundedConcurrentHashMap 这个对象引用了。

往下继续点开发现基本都是查询语句,发现每个查询语句都是一样的,就是后面in的参数不同,继续点开其他的发现都是这个语句

sql 复制代码
select count(generatedAlias0) from BizReportCatalogAttach as generatedAlias0 where generatedAlias0.bizTableFillId in (:param0_0, :param0_1, :param0_2, .... , :param0_68)

点开这个sql语句的参数发现 in 里面有3万多个参数,其他的SQL语句都是一样,就是 in 的参数不一样所以被缓存起来了

2.结论

至此,可以判断是被 Hibernate QueryPlanCache 查询计划JPQL缓存导致的问题。里面的SQL每次执行时随着in的参数不同导致Hibernate重复缓存SQL

hibernate会缓存sql语句以减少重复编译,便于直接命中提高效率。这个缓存默认QueryPlanCache的map entry默认容量上限是2048,且在使用in时,只要in后面的参数有任何一个不一样的,就会视为不同的语句而保存下来。

3.解决办法

通过配置Hibernate缓存sql语句的最大个数配置来限制缓存个数

在application.properties中添加如下配置

yaml 复制代码
#指定Hibernate查询计划缓存sql语句的最大个数, 默认2048, 详见org.hibernate.engine.query.spi.QueryPlanCache
spring.jpa.properties.hibernate.query.plan_cache_max_size=64
#指定Hibernate查询计划参数元数据缓存的最大大小, 管理缓存中ParameterMetadata实例的数量(默认为128), 详见org.hibernate.engine.query.spi.QueryPlanCache
spring.jpa.properties.hibernate.query.plan_parameter_metadata_max_size=32

如果hibernate 5.2.17+时,还可以添加此配置以减少IN子句的SQL计划缓存。

参考文档:https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html

yaml 复制代码
#Hibernate可以根据参数格式的几何算法进行生成缓存,例如生成2个参数、4个参数、2^2个参数的SQL,从而优化IN子句的使用,减少不必要的SQL计划缓存,避免因大量使用IN查询而导致内存溢出的问题‌
spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true

添加配置后通过debug启动可以在这里打断点看下我们的配置是否生效,我们在这里能看到上面很熟悉的三个身影:SessionFactoryImplementor、QueryPlanCache、BoundedConcurrentHashMap 这三个类,SessionFactoryImplementor 的引用占用了85.53%的堆空间

相关推荐
一诺加油鸭15 分钟前
若依后端系统集成 Swagger 接口文档功能
java·开发语言
云烟成雨TD21 分钟前
Spring AI Alibaba 1.x 系列【40】多智能体核心模式 - 智能体作为工具(Agent as Tool)
java·人工智能·spring
测试员周周33 分钟前
【踩坑系列3】飞书机器人集体“失联“?3 个 Gateway 进程让我差点崩溃!一个测试老兵的排查实录
java·python
aq553560034 分钟前
Laravel9.x新特性全解析
java·开发语言·数据库
齿轮34 分钟前
Agent 管理范式演进:从管一句话到管整个系统
人工智能·后端
亦暖筑序38 分钟前
AI 客服系统升级实战:多 Agent 路由 + 多轮记忆 + 敏感词过滤
java·后端
啷咯哩咯啷42 分钟前
纯本地运行的私人文档知识库
前端·人工智能·后端
Determined_man43 分钟前
项目中异常什么时候打印错误和抛出?
后端
zhangzeyuaaa1 小时前
深入理解 Python GIL:从机制到释放时机
java·网络·python
阿丰资源1 小时前
基于SpringBoot的房产销售系统设计与实现(附源码+数据库+文档,一键运行)
数据库·spring boot·后端