From 4c114e93f4c14ee17052a558c86fa1011716c6c7 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 24 May 2022 15:46:34 -0400 Subject: [PATCH] Merge pull request #543 from basil/JENKINS-63766 [JENKINS-63766] Work around JDK-8231454 (cherry picked from commit 0449852ee36f3abb53d4908a5b533b2d4d146c26) --- .../workflow/cps/CpsFlowExecution.java | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecution.java b/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecution.java index 8836ab696..97a8a3c63 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecution.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowExecution.java @@ -52,6 +52,7 @@ import hudson.model.Action; import hudson.model.Result; import hudson.util.Iterators; +import io.jenkins.lib.versionnumber.JavaSpecificationVersion; import jenkins.model.CauseOfInterruption; import jenkins.model.Jenkins; import org.jboss.marshalling.Unmarshaller; @@ -1316,6 +1317,7 @@ private static void cleanUpLoader(ClassLoader loader, Set encounter if (encounteredClasses.add(clazz)) { LOGGER.log(Level.FINER, "found {0}", clazz.getName()); Introspector.flushFromCaches(clazz); + cleanUpClassInfoCache(clazz); cleanUpGlobalClassSet(clazz); cleanUpClassHelperCache(clazz); cleanUpObjectStreamClassCaches(clazz); @@ -1376,6 +1378,44 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws } } + private static void cleanUpClassInfoCache(Class clazz) { + JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM(); + if (current.isNewerThan(new JavaSpecificationVersion("1.8")) + && current.isOlderThan(new JavaSpecificationVersion("16"))) { + try { + // TODO Work around JDK-8231454. + Class classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo"); + Field cacheF = classInfoC.getDeclaredField("CACHE"); + try { + cacheF.setAccessible(true); + } catch (RuntimeException e) { // TODO Java 9+ InaccessibleObjectException + /* + * Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". + * Until core adds this to its --add-opens configuration, and until that core + * change is widely adopted, avoid unnecessary log spam and return early. + */ + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); + } + return; + } + Object cache = cacheF.get(null); + Class cacheC = Class.forName("com.sun.beans.util.Cache"); + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE."); + } + Method removeM = cacheC.getMethod("remove", Object.class); + removeM.invoke(cache, clazz); + } catch (ReflectiveOperationException e) { + /* + * Should never happen, but if it does, ensure the failure is isolated to this + * method and does not prevent other cleanup logic from executing. + */ + LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); + } + } + } + private static void cleanUpGlobalClassSet(@NonNull Class clazz) throws Exception { Class classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet"); @@ -1423,13 +1463,16 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class clazz) thro for (String cacheFName : new String[] {"localDescs", "reflectors"}) { Field cacheF = cachesC.getDeclaredField(cacheFName); cacheF.setAccessible(true); - ConcurrentMap>, ?> cache = (ConcurrentMap) cacheF.get(null); - Iterator>, ?>> iterator = cache.entrySet().iterator(); - while (iterator.hasNext()) { - if (iterator.next().getKey().get() == clazz) { - iterator.remove(); - LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName}); - break; + Object cache = cacheF.get(null); + if (cache instanceof ConcurrentMap) { + // Prior to JDK-8277072 + Iterator>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator(); + while (iterator.hasNext()) { + if (iterator.next().getKey().get() == clazz) { + iterator.remove(); + LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName}); + break; + } } } }