背景
groovy用来和java集成,作为动态规则引擎使用,是非常不错的一个选择。简单来说,就是用java来执行一段groovy代码。可以通过一个简单的数据库配置,来动态的执行某段脚本。这样就可以实时得更改脚本,java就可以动态调用这段代码,从而达到灵活的在线变换的规则引擎。
坑
假如不做任何优化的话,那么每次java执行一次groovy脚本,都会动态生成一个class,将导致class越来越多,最终导致JVM进行perm区爆满的问题。
测试
用以下脚本循环执行groovy代码
while(true){
Binding binding = new Binding();
binding.setvariable("x",10);
binding.setvariable("language","Groovy");
groovyshell shell = new groovyshell(binding);
Object value = shell.evaluate("return x*2");//反复执行这段groovy脚本
System.out.println(value);
}
class检测
通过jconsole可以看到,class的数量线性增加,到了一定数量后就触发GC导致应用异常。
1、为什么Groovy每执行一次脚本,都会生成一个脚本对应的class对象?
因为一个ClassLoader对于同一个名字的类只能加载一次,都由GroovyClassLoader加载,那么当一个脚本里定义了C这个类之后,另外一个脚本再定义一个C类的话,GroovyClassLoader就无法加载了。
2、为什么InnerLoader加载的对应无法通过gc清理掉?
大家都知道,JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载:1. 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例;2. 加载该类的ClassLoader已经被GC;3. 该类的java.lang.class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。
在GroovyClassLoader代码中有一个class对象的缓存,进一步跟下去,发现每次编译脚本时都会在Map中缓存这个对象,即:setClassCacheEntry(clazz)。每次groovy编译脚本后,都会缓存该脚本的Class对象,下次编译该脚本时,会优先从缓存中读取,这样节省掉编译的时间。这个缓存的Map由GroovyClassLoader持有,key是脚本的类名,这就导致每个脚本对应的class对象都存在引用,无法被gc清理掉。
解释
每次groovy编译脚本的时候,都会生成一个名称为”script” + System.currentTimeMillis() + Math.abs(text.hashCode()) + “.groovy”的class对象。而此对象又会被map缓存起来,key就是刚才的名称,这样导致gc无法回收,从而导致fullgc
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。