|
52 | 52 | import hudson.model.Action; |
53 | 53 | import hudson.model.Result; |
54 | 54 | import hudson.util.Iterators; |
| 55 | +import io.jenkins.lib.versionnumber.JavaSpecificationVersion; |
55 | 56 | import jenkins.model.CauseOfInterruption; |
56 | 57 | import jenkins.model.Jenkins; |
57 | 58 | import org.jboss.marshalling.Unmarshaller; |
@@ -1298,6 +1299,7 @@ private static void cleanUpLoader(ClassLoader loader, Set<ClassLoader> encounter |
1298 | 1299 | if (encounteredClasses.add(clazz)) { |
1299 | 1300 | LOGGER.log(Level.FINER, "found {0}", clazz.getName()); |
1300 | 1301 | Introspector.flushFromCaches(clazz); |
| 1302 | + cleanUpClassInfoCache(clazz); |
1301 | 1303 | cleanUpGlobalClassSet(clazz); |
1302 | 1304 | cleanUpClassHelperCache(clazz); |
1303 | 1305 | cleanUpObjectStreamClassCaches(clazz); |
@@ -1358,6 +1360,44 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws |
1358 | 1360 | } |
1359 | 1361 | } |
1360 | 1362 |
|
| 1363 | + private static void cleanUpClassInfoCache(Class<?> clazz) { |
| 1364 | + JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM(); |
| 1365 | + if (current.isNewerThan(new JavaSpecificationVersion("1.8")) |
| 1366 | + && current.isOlderThan(new JavaSpecificationVersion("17"))) { |
| 1367 | + try { |
| 1368 | + // TODO Work around JDK-8231454. |
| 1369 | + Class<?> classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo"); |
| 1370 | + Field cacheF = classInfoC.getDeclaredField("CACHE"); |
| 1371 | + try { |
| 1372 | + cacheF.setAccessible(true); |
| 1373 | + } catch (RuntimeException e) { // TOOD Java 9+ InaccessibleObjectException |
| 1374 | + /* |
| 1375 | + * Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". |
| 1376 | + * Until core adds this to its --add-opens configuration, and until that core |
| 1377 | + * change is widely adopted, avoid unnecessary log spam and return early. |
| 1378 | + */ |
| 1379 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 1380 | + LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); |
| 1381 | + } |
| 1382 | + return; |
| 1383 | + } |
| 1384 | + Object cache = cacheF.get(null); |
| 1385 | + Class<?> cacheC = Class.forName("com.sun.beans.util.Cache"); |
| 1386 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 1387 | + LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE."); |
| 1388 | + } |
| 1389 | + Method removeM = cacheC.getMethod("remove", Object.class); |
| 1390 | + removeM.invoke(cache, clazz); |
| 1391 | + } catch (ReflectiveOperationException e) { |
| 1392 | + /* |
| 1393 | + * Should never happen, but if it does, ensure the failure is isolated to this |
| 1394 | + * method and does not prevent other cleanup logic from executing. |
| 1395 | + */ |
| 1396 | + LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); |
| 1397 | + } |
| 1398 | + } |
| 1399 | + } |
| 1400 | + |
1361 | 1401 | private static void cleanUpGlobalClassSet(@NonNull Class<?> clazz) throws Exception { |
1362 | 1402 | Class<?> classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there |
1363 | 1403 | Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet"); |
|
0 commit comments