前言
近期经常使用MAT分析Esper组件8.3.0的内存问题,总结了一些经验和方法,特此分享一下。基础功能不再赘述,此次主要分享分析方法与OQL的使用。
调整MAT配置
软件版本为1.15.0
# MAC配置修改方式,Win自行寻找
vim /Applications/MemoryAnalyzer.app/Contents/Eclipse/MemoryAnalyzer.ini
# 添加一行-Xmx20480m
内存泄露分析案例
- 选择Componet Report -> next -> 输入要查询的组件包名,可以使用.*作为通配符 -> Finsh -> 等待结果 -> 选择Top Consumers,找到占用内存较大的组件。
- 分析(a)组件,在Histogram组件搜索
ByteArrayProvidingClassLoader,右键单击 -> List objects -> with outgoing references
- 分析该ClassLoader的内容,按照Retained Heap倒序,点击占用较大的部分展开。可以看到是ConcurentHashMap类型的classes变量占用了绝大部分空间,此Map的val为byte数组,内容类似于二进制的class文件,右键单击Copy -> Save Value To File
- 将导出的文件任意命名为xxx.class,放入IDEA中反编译。发现此段代码为Esper组件通过专有的EPL语言动态生成的二进制代码,接下来需要确定为何有如此多的二进制代码,以及这些代码和现有的业务有何关系。
- 通过对现有代码工程的掌握,Esper与业务EPL的关系在
RuleCepConfigMeta中记录,可以通过OQL查询,但是此处记录的是全量的Esper规则与EPL,应用中并未全部加载。
# .*在MAT中类似于正则表达式的*
SELECT toString(id), toString(strategyId), toString(name), toString(rule) FROM ".*.RuleCepConfigMeta"
- 通过了解Esper组件,发现Esper动态生成对象均在generated包中,且该包的结构在逐层解析下deploymentId与
RuleCepConfigMeta的Id相同,使用OQL查询并去重。结果中存在1521条规则在运行时进行了EPL编译并存储到堆内存中。
SELECT DISTINCT id FROM OBJECTS ( SELECT toString(n.statementResultService.epStatement.statementContext.deploymentId) AS id FROM INSTANCEOF "generated.*" n ) WHERE (id != "null")
- 此为当前内存中常驻的部分,还有消费的数据未进行分析。当前应用中主要消费Kafka消息并转换为Fact对象,当前内存中有哪些Fact对象也是值得关注的。通过步骤2的方法,搜索Fact并打开。此对象结构较为简单,包含data,values的Map类型变量且持有相同引用。而且内部包含了时间戳,可以通过时间戳来判断数据的消费时间。使用OQL查询当前对象中最外层Map的数据内容。
SELECT toString(n.key), toString(n.value) FROM OBJECTS ( SELECT OBJECTS table.@referenceArray FROM OBJECTS ( SELECT OBJECTS values FROM ".*Fact" ) ) n
- 将整理出来的信息进行整合,可以通过MAT的导出CSV功能,将业务相关内容通过其他渠道 + 代码加工整理为Excel进行后续分析。
- 通过分析两次即应用正常运行时期与应用卡顿时期的Excel数据,表中的堆积数量(7步中的Fact对象timestamp与dump时间相差10分钟的数据量)有显著上升,此部分数据还需要重点分析。选择Fact对象的incoming references,逐层查看引用关系,发现被LengthWindowView引用。Esper提供了过期数据清理机制,但是内存中的已过期数据仍被Esper组件持有,猜测清理机制未生效导致内存泄露。
- 线下分析测试,发现Esper组件在每次做过期数据清理时存在NPE的问题,且该问题仅在EPL中使用last()或window()函数时出现。且在Github社区发现较新的8.9.0版本也有人反馈存在此问题,升级大概率并不能解决问题且可能会引入其他不可预测的问题。Debug动态生成的代码发现有一个List类型的成员变量未初始化导致的问题,通过了解动态生成代码的功能将此处补全解决了问题,同时提了issue反馈给社区。