java调用groovy如何避免OOM

news2025/3/15 2:10:49

首先我们要清楚java执行groovy的逻辑,这里我们采用了GroovyClassLoader的方法,因为它能缓存编译结果,不用每次执行相同的脚本都需要重新编译,提升执行效率

GroovyCodeSource groovyCodeSource = new GroovyCodeSource(context.getScriptContent(), scriptMd5DStr + ".groovy", "groovy/script/function"); 
Class clazz = groovyClassLoader.parseClass(groovyCodeSource, true);

GroovyCodeSource构造函数的第二个参数是我们执行groovy脚本的脚本名称,这个结合parseClass的第二个参数,表示是否缓存我们本地的编译结果,这样当我们的脚本没有发生变化时,可以无需编译直接从缓存中拿到之前编译过的脚本直接执行。

我们先来看一下执行一段普通脚本(没有引入jar包)的类加载器的变化

运行前:

运行后

通过对比我们可以看出,我们运行一次脚本多出了一个GroovyClassLoader和GroovyClassLoader$InnerLoader的类加载器,如果我们频繁的运行脚本,哪怕是一样的脚本,我们的类加载器会越来越多,进而导致(jdk1.8为例)

java.lang.OutOfMemoryError: Metaspace

那有什么解决的方法呢?

  • 首先我们要明确如果运行相同的代码是没有必要使用不同的GroovyClassLoader的,所以我们要对GroovyClassLoader进行缓存,这样,针对相同脚本的运行,我们将不会生成新的类加载器。
  • 其次如果是不同脚本的执行我们将面临之前同样的问题,所以我们需要一个类加载器的回收方法,幸运的是GroovyClassLoader提供了close()方法能清楚缓存以及其加载器。当然有的小伙伴会有这种想法既然有这么好的方法,为啥我不在每次执行完这个方法后清除类加载器就好了么?这样只会导致jvm频繁的进行垃圾回收进而导致用户进程的迟缓甚至宕机。

我们可以看下close方法的效果,我们这边生成了三个GroovyClassLoader,其中有一个我已经调用了close方法,我们需要进行一次垃圾回收,看是否能达到我们的目的(调用close方法只是标记了类加载器不可用,真正回收需要FullGC,这里我们模拟一下FullGC的情况)

毫无疑问我们得到了我们想要的结果。

  • 然后我们缓存了类加载器,也知道了清除类加载器的方法,那我们什么时候关闭类加载器呢?才能保证程序平稳运行。这里我们引入guava的一个并发map
public static ConcurrentLinkedHashMap<String, GroovyClassLoader> classLoaderCache = new ConcurrentLinkedHashMap.Builder<String, GroovyClassLoader>() .maximumWeightedCapacity(512).weigher(Weighers.singleton()).listener( (key, value) -> { try { //当缓存失效时,关闭classLoader,这里关键点,只有发生full gc的时候才会回收类加载器,所以jvm一定要给元空间配置边界 value.close(); } catch (IOException e) { throw new RuntimeException(e); } } ).build();

这个map可以根据容量配置一个最近最久未使用的算法,将长时间未使用的类加载器先行销毁,这样即保留了热点数据,又清理了内存,正如代码中所说,我们需要给云空间配置最大值,这样让空间不足时才会发生full gc进而回收内存,否则云空间会直接膨胀占用系统内存。这里还有一点需要说明的是,我们缓存的key是以脚本md5后的字符串为key,当我们的脚本中存在变量时,因为变量值不一样而导致脚本不一样从而生成新的缓存是积极浪费的。

vars.put("b",${a}+1);

针对上面的方法我们可以是使用绑定传参的方式,这样经过md5后脚本是一致的不会生成新的类加载器

groovyScript.setBinding(binding); 
Map<String, Object> runParams = context.getVars(); 
binding.setVariable(context.getParamBindName(), vars);
vars.put("b",vars.get("a")+1);

通过上述的手段我们从一定角度上解决了java调用groovy脚本的oom问题,但是有一种情况我们需要额外分析,当groovy脚本调用jar包里的方法的时候

当我们引入jar包时,我们通过

groovyClassLoader.addClasspath(localJar);

将jar包加入GroovyClassLoader的classpath,我们调用后发现,除了之前的classloader,新生成了一个CallSiteClassLoader,真正去加载jar包的其实是这个类加载器,我们使用之前的方法关闭GroovyClassLoader试试

不幸的是CallSiteClassLoader无法回收,groovy也没有提供removeClassPath的方法,我们只能另辟蹊径去处理这个了,好在平台使用jar包调用的地方不多,虽然不能完全避免发生OOM的可能,但是将其尽发生可能的时间拉长,是我们需要解决的问题。

  • 首先,我们当然也需要缓存脚本,为了和普通脚本区分,我们需要另外一个concurrentHashMap来存储我们的脚本,为了尽可能少的生成GroovyClassLoader,我们的map里的key使用的是业务id,而不是脚本的hash值
  • 其实,按照我们业务逻辑,只有自定义函数,或者调用自定义函数的部分会和jar包打交道,所以我们将不采用以往拼接自定义函数的方式来执行脚本,而是将自定义函数执行完的结果返回给脚本,例如:

def a = {{getInfo(1))}} vars.put("a",a)

以前我们的脚本是

def getInfo(){ return a+1; } def a = getInfo(1)) vars.put("a",a)

而我们现在针对包含jar包的函数,则会先运行函数,然后直接赋值给变量,这样独立运行的自定义脚本将不会生成新的类加载器。

  • 最后,我们还有一个关键问题点未解决,当我们的jar更新后,我们使用同一个类加载器将无法加载新的jar包,需要我们重启script-agent服务达到更新jar包的方法,针对这个问题,这里就需要讲一下我们这里的设计点,以前的jar包是每次执行脚本发现有关联jar时都回去文件中心拉取文件,当然文件哪怕是更新了也不会重新加载,这个每次拉的过程会比较耗时,我们这里使用mq做了一个异步功能,当jar发生变更时,agent监听这个事件就会在本地jar文件库做一个相应的变更,所以,我们针对上述问题就可以这样解决,当监听到jar是更新的时候,我们根据业务id找到缓存的GroovyClassLoader销毁,然后生成新的GroovyClassLoader,这样就可以加载新的jar包到程序中,后续的调用都会走新包的逻辑。

以上就是整个groovy脚本执行的设计,虽然没有百分百的根治OOM的问题,但是大部分场景下已经够用,也大大降低了程序因为脚本调用产生OOM的可能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/103058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode HOT 100 —— 437. 路径总和 III

题目 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点到子节点…

WebSocket新一代推送技术及Java Web实现

WebSocket简介 很多网站为了实现推送技术&#xff0c;所用的技术都是轮询。轮询是在特定的时间间隔&#xff08;如每1秒&#xff09;&#xff0c;由浏览器对服务器发出HTTP请求&#xff0c;然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点&#…

Visual Studio 控制台程序世界杯足球C语言版

Visual Studio 控制台程序世界杯足球C语言版程序之美前言主体运行效果核心代码逻辑分析结束语程序之美 前言 一年一度的世界杯如期而至&#xff0c;相信很多球迷小伙伴们一定不会错过这个难得的好机会&#xff0c;大饱眼福&#xff0c;美美的看上几场。 说起国际足联世界杯&a…

架构师必读 —— 逻辑模型(9)

逻辑树分析法 整理信息时&#xff0c;釆用逐条列举的方式是比较方便的。逐条列举的优点是可以简练地整理要点&#xff0c;利于缩小论点的范围&#xff0c;也方便项目的分类。但是&#xff0c;逐条列举也有缺点&#xff0c;就是遗漏和重复不太容易被发现&#xff0c;难以判断是否…

健身房训练计划

文章目录参考资料前言新手健身的首要任务训练计划关于组数、次数、重量和组间休息时间周一胸肌和手臂参考资料 男生健身房一周训练计划, 收藏了 最高效的健身房一周训练计划表&#xff0c;附完整动图教学&#xff01; 前言 新手健身的首要任务 1、了解和熟练掌握锻炼身体各…

从源码的角度解析Spirng 的import标签和alias标签的处理

在上一篇博客《源码深度剖析Spring Bean标签的解析及注册》中描述了bean 标签的解析以及注册。 而Spring 的XML 配置文件职工包括import标签、alias标签、bean标签和beans 标签&#xff0c;那么这篇博客就针对剩余的import 标签 、alias标签进行处理。由于beans标签类似于impor…

docker基础

目录常用命令Docker命令镜像命令(image)容器命令(container)容器数据卷命令挂载具名挂载和匿名挂载查看卷信息数据卷权限dockerfile挂载新建dockerfile文件build构建dockerfile的镜像用自定义的镜像启动容器查看主机挂载路径容器共享数据卷多容器创建DockerFile文件创建dockerf…

三大管理法则—鱼缸法则、木桶效应、热炉法则

一、鱼缸法则 鱼缸法则运用到管理中&#xff0c;就是增加工作的透明度。 各项工作有了透明度&#xff0c;领导者的行为就会置于全体下属的监督之下&#xff0c;就会有效地防止领导者滥用权力&#xff0c;从而强化领导者的自我约束机制。 项目管理中&#xff0c;管理者可以使…

AI 编剧大师 #Dramatron

DeepMind 近日发布了一款名为 “Dramatron” 的新 AI 写作模型&#xff0c;用上它人人都可以变身编剧或者作家&#xff0c;只需要给出一句话大纲&#xff0c; Dramatron 就能生成包括标题、角色列表、情节、场景描述和对话的完整电影 or 戏剧脚本&#xff0c;并且连贯性极强。简…

解决安全生产知识题库小程序加载超大数据无法渲染的问题

遇到问题 在搭建安全生产知识竞赛题库小程序的时候&#xff0c;由于题库的题量太大了&#xff0c;一次性加载setData或者多次concat后setData&#xff0c;其实它俩都是一次性setData&#xff0c;这样就会造成加载超大数据无法渲染空白的问题。 安全生产知识竞答 解决微信小程序…

计算机毕设Python+Vue学生日常事务管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

line-height:1的存在意义是什么(v1)

一、line-height:1的存在意义是什么&#xff08;v1&#xff09; 1. 加不加这段代码的区别是什么&#xff1f; 示例代码 <!DOCTYPE html> <html lang"en"> <style>p{line-height:1;} /* 随时注释的line-height:1 */p{margin:0;background-color:…

《罗马革命》豆瓣 9.1 从恺撒大帝到屋大维

《罗马革命》 关于作者 本书的作者罗纳德•塞姆&#xff0c;是英国牛津大学古罗马历史教授&#xff0c;被誉为20世纪西方世界最出色的罗马史学家。 关于本书 本书的作者抛弃了古罗马史家的论述&#xff0c;从政治史研究的角度&#xff0c;观察罗马革命的前因后果&#xff0c…

Java——记录BigDecimal与0比较的一个坑

文章目录前言问题解决问题解决前言 在之前做的一个项目中&#xff0c;为了保证BigDecimal在除数 divide时&#xff0c;如果被除数为0&#xff0c;出现java.lang.ArithmeticException: / by zero 报错问题&#xff0c;写了一个对比。具体代码如下&#xff1a; public static B…

Promise(一) 介绍及基本使用+API

目录 1.Promise 的理解和使用 1.1. Promise 是什么? 1.1.1. 理解 1.2. 为什么要用 Promise? 1.2.1. 指定回调函数的方式更加灵活 1.2.2. 支持链式调用, 可以解决回调地狱问题 1.3 promise初体验 1.4 Promise实践练习——fs读取文件 1.5 Promise实践练习——Ajax请求…

论文精讲 | 一种隐私保护边云协同训练

作者&#xff1a;王森、王鹏、姚信、崔金凯、胡钦涛、陈仁海、张弓 &#xff5c;单位&#xff1a;2012实验室理论部 论文标题 MistNet: Towards Private Neural Network Training with Local Differential Privacy 论文链接 https://github.com/TL-System/plato/blob/main/…

安装MYSQL 社区版 mysql 8.0.30

https://dev.mysql.com/downloads/ 上面地址下载 安装社区版&#xff0c;选择开发者选项 mysql社区版 8.0.30&#xff0c; 直接都下一步&#xff0c;下一步的安装 安装进度一直在显示 安装完成&#xff0c; 下一步 开发者 输入密码&#xff1a; 增加用户 sa 又是一番执行 …

Effective C++(一):让自己习惯C++

个人读书记录&#xff0c;不适用教学内容。 目录 条款01&#xff1a;视C为一个语言联邦 条款02&#xff1a;尽量以const&#xff0c;enum&#xff0c;inline替换#define 条款03&#xff1a;尽可能使用const 所谓的"顶层const"和"底层const" const返回…

微服务框架 SpringCloud微服务架构 微服务面试篇 54 微服务篇 54.7 Sentinel的线程隔离与Hystix的线程隔离有什么差别

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 微服务面试篇 文章目录微服务框架微服务面试篇54 微服务篇54.7 Sentinel的线程隔离与Hystix的线程隔离有什么差别?54.7.1 Sentinel与Hystix…

计算机毕设Python+Vue学生社团管理(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…