通过监控工平台,查看到定价集群存在少量FullGC的情况,FullGC对于程序员来说其实是不能忍的,于是开始排查。
监控提示是MetaSpace发生fullGC,Metaspace中的类需要满足什么条件才能够被当成垃圾被卸载回收?
条件还是比较严苛的,需同时满足如下三个条件的类才会被卸载:
初步推测是有不停的动态创建类的过程,且有类未被回收;后来考虑到定价中存在轻量的表达式引擎AviatorEvaluator,会存在此过程;
于是dump出metaspace和heap
可以看到,存在非常多的Scrip${timestamp}${idx}类型的类
重复加载的类中,Top都是关于lambda表达式的
且指向了com.googlecode.aviator包 - 表达式引擎
找到了我的代码:
/**
* 表达式处理工具类
*
* @author Hollis
*/
public class ExpressionUtil {
public static AviatorEvaluatorInstance aviatorEvaluator = AviatorEvaluator.getInstance();
static {
aviatorEvaluator.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
aviatorEvaluator.setOption(Options.ALWAYS_PARSE_INTEGRAL_NUMBER_INTO_DECIMAL, true);
}
public static boolean verify(String expression, Map<String, Object> params) {
return (Boolean)aviatorEvaluator.compile(expression).execute(params);
}
/**
* 表达式计算
* @param expression 表达式
* @param params 需要替换的表达式参数
* @return calculate result
*/
public static BigDecimal calculate(String expression, Map<String, Object> params) {
BigDecimal result = (BigDecimal)aviatorEvaluator.compile(expression).execute(params);
return result.setScale(6, RoundingMode.HALF_UP);
}
}
/**
* Compile a text expression to Expression Object without caching
*
* @param expression
* @return
*/
public Expression compile(final String expression) {
return compile(expression, false);
}
public final class AviatorEvaluatorInstance {
private volatile AviatorClassLoader aviatorClassLoader = initAviatorClassLoader();
private AviatorClassLoader initAviatorClassLoader() {
return AccessController.doPrivileged(new PrivilegedAction<AviatorClassLoader>() {
@Override
public AviatorClassLoader run() {
return new AviatorClassLoader(AviatorEvaluatorInstance.class.getClassLoader());
}
});
}
...
}
/**
* 编译过程
**/
private Expression innerCompile(final String expression, final String sourceFile,
final boolean cached) {
// 文法分析
ExpressionLexer lexer = new ExpressionLexer(this, expression);
// 初始化编码生成器
CodeGenerator codeGenerator = newCodeGenerator(sourceFile, cached);
// 语法解析器生成
ExpressionParser parser = new ExpressionParser(this, lexer, codeGenerator);
// 语法解析,实例化Class,最终的 Expression 对象
Expression exp = parser.parse();
if (getOptionValue(Options.TRACE_EVAL).bool) {
((BaseExpression) exp).setExpression(expression);
}
return exp;
}
// 初始化编码生成器
public CodeGenerator newCodeGenerator(final String sourceFile, final boolean cached) {
AviatorClassLoader classLoader = getAviatorClassLoader(cached);
return newCodeGenerator(classLoader, sourceFile);
}
/**
* Returns classloader
*
* @return
*/
public AviatorClassLoader getAviatorClassLoader(final boolean cached) {
if (cached) {
return this.aviatorClassLoader;
} else {
return new AviatorClassLoader(this.getClass().getClassLoader());
}
}
/**
* Returns CodeGenerator
*
* @return
*/
public CodeGenerator newCodeGenerator(final AviatorClassLoader classLoader,
final String sourceFile) {
switch (getOptimizeLevel()) {
case AviatorEvaluator.COMPILE:
ASMCodeGenerator asmCodeGenerator =
new ASMCodeGenerator(this, sourceFile, classLoader, this.traceOutputStream);
asmCodeGenerator.start();
return asmCodeGenerator;
case AviatorEvaluator.EVAL:
// 默认走EVAL
return new OptimizeCodeGenerator(this, sourceFile, classLoader, this.traceOutputStream);
default:
throw new IllegalArgumentException("Unknow option " + getOptimizeLevel());
}
}
public OptimizeCodeGenerator(final AviatorEvaluatorInstance instance, final String sourceFile,
final ClassLoader classLoader, final OutputStream traceOutStream) {
this.instance = instance;
this.sourceFile = sourceFile;
this.codeGen = new ASMCodeGenerator(instance, sourceFile, (AviatorClassLoader) classLoader,
traceOutStream);
}
// 编码生成器,会创建一个匿名的类
public ASMCodeGenerator(final AviatorEvaluatorInstance instance, final String sourceFile,
final AviatorClassLoader classLoader, final OutputStream traceOut) {
this.classLoader = classLoader;
this.instance = instance;
this.compileEnv = new Env();
this.sourceFile = sourceFile;
this.compileEnv.setInstance(this.instance);
// Generate inner class name
this.className = "Script_" + System.currentTimeMillis() + "_" + CLASS_COUNTER.getAndIncrement();
// Auto compute frames
this.classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// if (trace) {
// this.traceClassVisitor = new TraceClassVisitor(this.clazzWriter, new PrintWriter(traceOut));
// this.classWriter = new CheckClassAdapter(this.traceClassVisitor);
// } else {
// this.classWriter = new CheckClassAdapter(this.clazzWriter);
// }
visitClass();
}
public ExpressionParser(final AviatorEvaluatorInstance instance, final ExpressionLexer lexer,
final CodeGenerator codeGenerator) {
super();
this.scope = new ScopeInfo(0, 0, 0, 0, false, new ArrayDeque<DepthState>());
this.instance = instance;
this.captureFuncArgs = instance.getOptionValue(Options.CAPTURE_FUNCTION_ARGS).bool;
this.lexer = lexer;
this.lookhead = this.lexer.scan();
if (this.lookhead != null) {
this.parsedTokens++;
}
this.featureSet = this.instance.getOptionValue(Options.FEATURE_SET).featureSet;
if (this.lookhead == null) {
reportSyntaxError("blank script");
}
// 引用编码生成器
setCodeGenerator(codeGenerator);
// 设置解析器,编码生成器引用this
getCodeGeneratorWithTimes().setParser(this);
}
public Expression parse(final boolean reportErrorIfNotEOF) {
StatementType statementType = statements();
if (this.lookhead != null && reportErrorIfNotEOF) {
if (statementType == StatementType.Ternary) {
reportSyntaxError("unexpect token '" + currentTokenLexeme()
+ "', maybe forget to insert ';' to complete last expression ");
} else {
reportSyntaxError("unexpect token '" + currentTokenLexeme() + "'");
}
}
// 实例化
return getCodeGeneratorWithTimes().getResult(true);
}
@Override
public Expression getResult(final boolean unboxObject) {
end(unboxObject);
byte[] bytes = this.classWriter.toByteArray();
try {
Class<?> defineClass =
ClassDefiner.defineClass(this.className, Expression.class, bytes, this.classLoader);
Constructor<?> constructor =
defineClass.getConstructor(AviatorEvaluatorInstance.class, List.class, SymbolTable.class);
ClassExpression exp = (ClassExpression) constructor.newInstance(this.instance,
new ArrayList<String>(this.varTokens.keySet()), this.symbolTable);
exp.setLambdaBootstraps(this.lambdaBootstraps);
exp.setFuncsArgs(this.funcsArgs);
return exp;
} catch (ExpressionRuntimeException e) {
throw e;
} catch (Throwable e) {
if (e.getCause() instanceof ExpressionRuntimeException) {
throw (ExpressionRuntimeException) e.getCause();
}
throw new CompileExpressionErrorException("define class error", e);
}
}
结论就是,无缓存情况下,每次编译表达式都会生成一个AviatorClassLoader类加载器+一个匿名的Express类型的class,metaspace的空间会逐步占满
解决思路就是官方支持使用缓存的方式。在github上也有人提到过类似的问题。