|
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;
|
@@ -1316,6 +1317,7 @@ private static void cleanUpLoader(ClassLoader loader, Set<ClassLoader> encounter
|
1316 | 1317 | if (encounteredClasses.add(clazz)) {
|
1317 | 1318 | LOGGER.log(Level.FINER, "found {0}", clazz.getName());
|
1318 | 1319 | Introspector.flushFromCaches(clazz);
|
| 1320 | + cleanUpClassInfoCache(clazz); |
1319 | 1321 | cleanUpGlobalClassSet(clazz);
|
1320 | 1322 | cleanUpClassHelperCache(clazz);
|
1321 | 1323 | cleanUpObjectStreamClassCaches(clazz);
|
@@ -1376,6 +1378,44 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws
|
1376 | 1378 | }
|
1377 | 1379 | }
|
1378 | 1380 |
|
| 1381 | + private static void cleanUpClassInfoCache(Class<?> clazz) { |
| 1382 | + JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM(); |
| 1383 | + if (current.isNewerThan(new JavaSpecificationVersion("1.8")) |
| 1384 | + && current.isOlderThan(new JavaSpecificationVersion("16"))) { |
| 1385 | + try { |
| 1386 | + // TODO Work around JDK-8231454. |
| 1387 | + Class<?> classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo"); |
| 1388 | + Field cacheF = classInfoC.getDeclaredField("CACHE"); |
| 1389 | + try { |
| 1390 | + cacheF.setAccessible(true); |
| 1391 | + } catch (RuntimeException e) { // TODO Java 9+ InaccessibleObjectException |
| 1392 | + /* |
| 1393 | + * Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". |
| 1394 | + * Until core adds this to its --add-opens configuration, and until that core |
| 1395 | + * change is widely adopted, avoid unnecessary log spam and return early. |
| 1396 | + */ |
| 1397 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 1398 | + LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); |
| 1399 | + } |
| 1400 | + return; |
| 1401 | + } |
| 1402 | + Object cache = cacheF.get(null); |
| 1403 | + Class<?> cacheC = Class.forName("com.sun.beans.util.Cache"); |
| 1404 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 1405 | + LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE."); |
| 1406 | + } |
| 1407 | + Method removeM = cacheC.getMethod("remove", Object.class); |
| 1408 | + removeM.invoke(cache, clazz); |
| 1409 | + } catch (ReflectiveOperationException e) { |
| 1410 | + /* |
| 1411 | + * Should never happen, but if it does, ensure the failure is isolated to this |
| 1412 | + * method and does not prevent other cleanup logic from executing. |
| 1413 | + */ |
| 1414 | + LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); |
| 1415 | + } |
| 1416 | + } |
| 1417 | + } |
| 1418 | + |
1379 | 1419 | private static void cleanUpGlobalClassSet(@NonNull Class<?> clazz) throws Exception {
|
1380 | 1420 | Class<?> classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there
|
1381 | 1421 | Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet");
|
@@ -1423,13 +1463,16 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class<?> clazz) thro
|
1423 | 1463 | for (String cacheFName : new String[] {"localDescs", "reflectors"}) {
|
1424 | 1464 | Field cacheF = cachesC.getDeclaredField(cacheFName);
|
1425 | 1465 | cacheF.setAccessible(true);
|
1426 |
| - ConcurrentMap<Reference<Class<?>>, ?> cache = (ConcurrentMap) cacheF.get(null); |
1427 |
| - Iterator<? extends Entry<Reference<Class<?>>, ?>> iterator = cache.entrySet().iterator(); |
1428 |
| - while (iterator.hasNext()) { |
1429 |
| - if (iterator.next().getKey().get() == clazz) { |
1430 |
| - iterator.remove(); |
1431 |
| - LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName}); |
1432 |
| - break; |
| 1466 | + Object cache = cacheF.get(null); |
| 1467 | + if (cache instanceof ConcurrentMap) { |
| 1468 | + // Prior to JDK-8277072 |
| 1469 | + Iterator<? extends Entry<Reference<Class<?>>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator(); |
| 1470 | + while (iterator.hasNext()) { |
| 1471 | + if (iterator.next().getKey().get() == clazz) { |
| 1472 | + iterator.remove(); |
| 1473 | + LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName}); |
| 1474 | + break; |
| 1475 | + } |
1433 | 1476 | }
|
1434 | 1477 | }
|
1435 | 1478 | }
|
|
0 commit comments