QLExpress中function脚本导致内存泄漏

背景

生产环境运行一段时间后变得卡顿,排查后发现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方法名不一样,每次都要重复编译脚本,也会导致性能差;

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值