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