目录
前言:在生产环境中有一个后端程序多次报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%的堆空间