背景:
应用中内嵌了groovy引擎,会动态执行传入的表达式并返回执行结果
线上问题:
- 发现机器的fullGC从某个时候开始暴涨,并且一直持续;
- 登到机器上,用jstat -gcutil 命令观察,发现perm区一直是100%,fullGC无法回收;
- 将这台机器的内存dump出来进行分析;
- 在类视图中,发现大量的groovy.lang.GroovyClassLoader$InnerLoader;
- 在类加载器视图里面也看到大量的groovy的InnerLoader;
-
基本上可以定位问题在groovy脚本的加载处;
初步的问题分析:
groovy每执行一次脚本,都会生成一个脚本的class对象,并new一个InnerLoader去加载这个对象,而InnerLoader和脚本对象都无法在fullGC的时候被回收,因此运行一段时间后将PERM占满,一直触发fullGC。
因此,跟了一下groovy的编译脚本的源码:
脚本编译的入口是GroovyShell的parse方法:
public Script parse(GroovyCodeSource codeSource) throws CompilationFailedException { return InvokerHelper.createScript(parseClass(codeSource), this.context); }
所有的脚本都是由GroovyClassLoader加载的,每次加载脚本都会生成一个新的InnerLoader去加载脚本,但InnerLoader只是继承GroovyClassLoader,加载脚本的时候,也是交给GroovyClassLoader去加载:
创建新的innerLoader:
InnerLoader loader = (InnerLoader)AccessController.doPrivileged(new PrivilegedAction() { public GroovyClassLoader.InnerLoader run() { return new GroovyClassLoader.InnerLoader(GroovyClassLoader.this); } });
innerLoader继承GroovyClassLoader:
public static class InnerLoader extends GroovyClassLoader { private final GroovyClassLoader delegate;