QLExpress中function脚本导致内存泄漏

简介: 生产环境运行一段时间后变得卡顿,排查后发现GC日志中在频繁FullGC,内存分析发现有内存泄漏

背景

生产环境运行一段时间后变得卡顿,排查后发现GC日志中在频繁FullGC

内存分析

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
好家伙,String对象数量上,checkUser 相关内容就占了 317万多个,还剩下36万多个其它String对象;

关于这个 parallelLockMap

java.lang.ClassLoader#parallelLockMap
loadClass的过程中,会根据要加载的类的类名去获取一把锁,并保存到这个parallelLockMap中
在这里插入图片描述在这里插入图片描述在这里插入图片描述

初步结论

类加载器:
org.springframework.boot.loader.LaunchedURLClassLoader
加载了很多类名为 checkUser 的临时类,导致 classLoader里面的parallelLockMap数据增多,且无法及时回收

代码排查

找到业务代码中checkUserOrg与checkUserType相关的代码,得知是使用了QLExpress,脚本中定义的function方法名;
关于QlExpress:https://github.com/alibaba/QLExpress

问题复现

已提交issues:https://github.com/alibaba/QLExpress/issues/281
版本:jdk1.8 + QLExpress 3.2.4
pom.xml

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>QLExpress</artifactId>
            <version>3.2.4</version>
        </dependency>

问题复现代码:

public static void main(String[] args) throws Exception {
   
   
        ExpressRunner RUNNER = new ExpressRunner();
        DefaultContext<String, Object> defaultContext = new DefaultContext<String, Object>();
        defaultContext.put("org", new Object());

        Object result = RUNNER.execute("if (method_1650070704185950208()) {\n  return 0;\n } \n function method_1650070704185950208(){return false;}",
                defaultContext, null, true, false);
        System.out.println(result);

        result = RUNNER.execute("if (method_1650070704185950208()) {\n  return 0;\n } \n function method_1650070704185950208(){return true;}",
                defaultContext, null, true, false);
        System.out.println(result);

        result = RUNNER.execute("if (method_1650070704185950208()) {\n  return 1;\n } \n function method_1650070704185950208(){return true;}",
                defaultContext, null, true, false);
        System.out.println(result);
    }

debug观察:
((ExtClassLoader)DemoTempApplication.class.getClassLoader().parent).parallelLockMap
会发现 “method_1650070704185950208” 作为类名被尝试加载过
在这里插入图片描述

查看qlExpress源码

确实是会根据function方法名加载一个类,从com.ql.util.express.ExpressRunner#execute 跟代码到:
com.ql.util.express.parse.ExpressPackage#getClassInner
在这里插入图片描述

结论

1、业务代码中这个function方法名带有序列号时,执行次数越多,classLoader里面的parallelLockMap数据就越多,且无法回收;
2、后面排查代码的过程中还发现了一出容易出现内存泄漏的地方,就是QL脚本编译后的缓存也会无限增加,因为他是将QL脚本作为key,将解析后的指令集作为value存入一个Map中;

总的来说,还是属于QL脚本使用不够规范,用了唯一序列号拼接function方法名,导致方法名不同,脚本也就不同,也导致classLoader与QL指令集缓存会爆满;

修复

与QLExpress项目开发人员取得了联系,并沟通后得知:
1、他这里面的loadClass,只是编译时候的一个尝试过程,比如你写一个Map,他就会尝试给你加载 java.util.Map,看看这个类是否已存在;
2、动态脚本,应该是可以枚举的,脚本模板最好能固定住,变化的是参数就行,否则就因为function方法名不一样,每次都要重复编译脚本,也会导致性能差;

然后与自己公司的相关开发人员沟通:
只需要保证当前脚本内方法名不重复即可,不同脚本之间的方法名不需要特意区分;
所以调整了业务代码,脚本内有多个方法定义时,改用循环内使用索引下标值进行拼接;

相关文章
|
8月前
|
Oracle 关系型数据库 Linux
解决在linux服务器上部署定时自动查找cpu,内存,磁盘使用量,并将查询结果写入数据库的脚本,只能手动运行实现插库操作
问题描述:将脚本名命名为mortior.sh(以下简称mo),手动执行脚本后查询数据库,表中有相应的信息,放入自动执行队列中,脚本被执行,但是查询数据库,并没有新增数据。
57 0
|
8月前
|
Shell 测试技术 Linux
通过shell脚本进行linux服务器的CPU和内存压测
通过shell脚本进行linux服务器的CPU和内存压测
243 0
|
12天前
|
监控 Python
paramiko 模块 ---Python脚本监控当前系统的CPU、内存、根目录、IP地址等信息
paramiko 模块 ---Python脚本监控当前系统的CPU、内存、根目录、IP地址等信息
|
12天前
|
监控 Linux
Linux脚本之监控系统内存使用情况并给予警告
Linux脚本之监控系统内存使用情况并给予警告
|
2月前
|
存储 弹性计算 运维
调整虚拟机内存参数的 shell 脚本
【4月更文挑战第29天】
41 2
|
2月前
|
存储 弹性计算 运维
调整虚拟机内存参数的shell 脚本
【4月更文挑战第29天】
33 0
|
2月前
|
存储 监控 异构计算
【Python】GPU内存监控脚本
【Python】GPU内存监控脚本
|
2月前
|
监控 Shell
Shell脚本监控CPU、内存和硬盘利用率
Shell脚本监控CPU、内存和硬盘利用率
|
8月前
|
Shell
使用 shell 脚本清理内存 buff/cache
使用 shell 脚本清理内存 buff/cache
130 0
|
Shell Linux
编写一个小脚本程序memusage.sh,根据Free命令的结果结算内存占用率
编写一个小脚本程序memusage.sh,根据Free命令的结果结算内存占用率
89 1
编写一个小脚本程序memusage.sh,根据Free命令的结果结算内存占用率