groovy与java集成的坑

背景

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

你可能感兴趣的:(JAVA/JSP)