起因
公司有一个系统(shit code),上线之后,不定时的会崩溃,容器没有挂掉,然后就可以查看Docker容器的日志,硕大的java.lang.OutOfMemoryError: Java heap space
出现在了眼前。
准备分析
需要的工具:
- JetBrains IDEA 或者 Eclipse
- Java崩溃产生的转储文件(见下文)
一个电脑和一个人
添加JVM参数
如果服务在容器中,则需要配置挂载点,将容器内的转储目录映射到物理磁盘,以免造成容器重启丢失。
在启动命令中的-jar
前面加入-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/server.dump
,然后重启服务,等待下一次崩溃。
开始分析
到了某一个时间点,
终于崩溃了一次,然后到上述配置的路径中查看,发现有了一个server.dump
文件。这个就是我们要分析的文件。
第一步 准备阶段
我们需要将转储文件导出到自己的电脑中的一个空目录中(没有就新建一个),然后使用IDEA以项目的身份打开这个目录。然后找到左下角的Profiler
,如果没有就去View -> Tool Windows -> Profiler
点一下即可。
在Profiler
中,右侧窗口的左下角有一个Open Snapshot...
,点击这个按钮,然后打开我们下载下来的转储文件。
加载时间取决于转储文件的大小和电脑的性能
我这个转储文件足足有3GB左右的大小(
第二步 开始分析
IDEA直接就显示了直方图(Histogram)
我的很大,你忍一下
由此我们可以看到这几个列:
列名 | 含义 |
---|---|
Class | 无须多言,就是类 |
Count | 实例/对象数量 |
Shallow | 浅堆大小,对象本身占用的空间 |
Retained | 对象活跃时占用的空间或保留空间 |
我们可以看到:
java.lang.String
和java.util.HashMap$Node
的实例数量过多,数百万个。org.hibernate.internal.SessionFactoryImpl
的保留大小也达到了 295.05 MB。org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
的保留大小为 383.54 MB。
由以上信息可能得不出什么结果,我们继续追查,分析一下Biggest Objects。
如图我们可以看到已经按Retained排好序了,我们以第一项为例,因为占用的最多:
org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
的保留大小到达了383.66 MB,很大,我们继续展开,可以得到:
persistentEntities 持久化的内容较多,看似比较正常(诶数据库设计的也是shit),继续看下一项org.hibernate.internal.SessionFactoryImpl
:
我们可以看到queryPlanCache
占用了过大的保留空间,缓存了大量的查询,这或许不太正常,继续向下:
这里有很多比较大的查询,这值得深思,我找一个最大的进行分析:
嗯?735462个字符?这对吗?我们继续看看这个查询里都装了一些什么怪东西:
我嘞个 in-clause,怎么in了这么多,经过一番查找,根据 这篇文章 以及 文章中提到的另一篇文章 知道了一些线索:
在Hibernate中,带有SQL的IN子句的查询的项目中,Hibernate 会缓存这些已解析的 HQL 查询。
具体来说,Hibernate SessionFactoryImpl 包含 QueryPlanCache、queryPlanCache 和 parameterMetadataCache。
但事实证明,当 in子句的参数数量较大且变化较大时,这会成为一个问题。
初步定位问题
经过一系列的分析和查找,我们可以知道在Hibernate中,in-clause的条件会缓存下来,每次变化都会缓存,而且不能被垃圾回收,这种情况会一直持续下去,直到JVM的内存堆空间被填满,然后引发java.lang.OutOfMemoryError: Java heap space
。
建议采取的行动
- HQL和SQL的查询优化,尽量避免使用in-clause。
- 检查 Hibernate 是否有关于查询计划缓存大小或清理策略。
- 其实,我们还可以看到一些其他问题,比如:
java.lang.String
占用的空间也不小,所以还需对字符串处理的相关代码进行优化。
后续待更新
附加信息 or 参考来源
- https://technodibble.blogspot.com/2015/02/hibernate-in-clause-outofmemory.html
- https://dimovelev.blogspot.com/2015/02/performance-pitfalls-hibernate-query.html
- 根据Hibernate 的
HHH-13345
->HHH-10782
议题可得,查询计划缓存在清理之后依旧可以造成内存泄露。该议题已经修复,修复版本:5.3.5、5.4.0.RC1。
Comments | NOTHING