From 53f2d1d0942575d43e214b0c3ab3750c32b40ba6 Mon Sep 17 00:00:00 2001 From: danielsun1106 Date: Sat, 27 Oct 2018 19:38:01 +0800 Subject: [PATCH] GROOVY-8858: Refine GCL to avoid occupying Permanent Area/Metaspace repeatedly for same source code --- .../groovy/groovy/lang/GroovyClassLoader.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/groovy/groovy/lang/GroovyClassLoader.java b/src/main/groovy/groovy/lang/GroovyClassLoader.java index 90402ee1693..fd355248163 100644 --- a/src/main/groovy/groovy/lang/GroovyClassLoader.java +++ b/src/main/groovy/groovy/lang/GroovyClassLoader.java @@ -41,6 +41,7 @@ import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.runtime.EncodingGroovyMethods; import org.codehaus.groovy.runtime.IOGroovyMethods; import org.codehaus.groovy.runtime.InvokerHelper; import org.codehaus.groovy.runtime.memoize.EvictableCache; @@ -66,6 +67,7 @@ import java.net.URLDecoder; import java.security.AccessController; import java.security.CodeSource; +import java.security.NoSuchAlgorithmException; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; @@ -315,8 +317,13 @@ public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedExc * @return the main class defined in the given script */ public Class parseClass(final GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException { + // it's better to cache class instances by the source code + // GCL will load the unique class instance for the same source code + // and avoid occupying Permanent Area/Metaspace repeatedly for same source code + String cacheKey = genSourceCacheKey(codeSource); + return ((StampedCommonCache) sourceCache).getAndPut( - codeSource.getName(), + cacheKey, new EvictableCache.ValueProvider() { @Override public Class provide(String key) { @@ -327,6 +334,31 @@ public Class provide(String key) { ); } + private String genSourceCacheKey(GroovyCodeSource codeSource) { + String strToDigest; + + String scriptText = codeSource.getScriptText(); + if (null != scriptText) { + strToDigest = "scriptText:" + scriptText; + CodeSource cs = codeSource.getCodeSource(); + if (null != cs) { + strToDigest = strToDigest + "/codeSource:" + cs; + } + } else { + // if the script text is null, i.e. the script content is invalid + // use the name as cache key for the time being to trigger the validation by `groovy.lang.GroovyClassLoader.validate` + // note: the script will not be cached due to the invalid script content, + // so it does not matter even if cache key is not the md5 value of script content + strToDigest = "name:" + codeSource.getName(); + } + + try { + return EncodingGroovyMethods.md5(strToDigest); + } catch (NoSuchAlgorithmException e) { + throw new GroovyRuntimeException(e); // should never reach here! + } + } + private Class doParseClass(GroovyCodeSource codeSource) { validate(codeSource); Class answer; // Was neither already loaded nor compiling, so compile and add to cache.