|
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("16"))) { |
| 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) { // TODO 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"); |
@@ -1405,13 +1445,16 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class<?> clazz) thro |
1405 | 1445 | for (String cacheFName : new String[] {"localDescs", "reflectors"}) { |
1406 | 1446 | Field cacheF = cachesC.getDeclaredField(cacheFName); |
1407 | 1447 | cacheF.setAccessible(true); |
1408 | | - ConcurrentMap<Reference<Class<?>>, ?> cache = (ConcurrentMap) cacheF.get(null); |
1409 | | - Iterator<? extends Entry<Reference<Class<?>>, ?>> iterator = cache.entrySet().iterator(); |
1410 | | - while (iterator.hasNext()) { |
1411 | | - if (iterator.next().getKey().get() == clazz) { |
1412 | | - iterator.remove(); |
1413 | | - LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName}); |
1414 | | - break; |
| 1448 | + Object cache = cacheF.get(null); |
| 1449 | + if (cache instanceof ConcurrentMap) { |
| 1450 | + // Prior to JDK-8277072 |
| 1451 | + Iterator<? extends Entry<Reference<Class<?>>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator(); |
| 1452 | + while (iterator.hasNext()) { |
| 1453 | + if (iterator.next().getKey().get() == clazz) { |
| 1454 | + iterator.remove(); |
| 1455 | + LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName}); |
| 1456 | + break; |
| 1457 | + } |
1415 | 1458 | } |
1416 | 1459 | } |
1417 | 1460 | } |
|
0 commit comments