|
36 | 36 | import hudson.model.Run; |
37 | 37 | import hudson.model.TaskListener; |
38 | 38 | import hudson.util.FormValidation; |
| 39 | +import io.jenkins.lib.versionnumber.JavaSpecificationVersion; |
39 | 40 |
|
40 | 41 | import java.beans.Introspector; |
41 | 42 | import java.io.Serializable; |
@@ -207,6 +208,7 @@ private static void cleanUpClass(Class<?> clazz, Set<ClassLoader> encounteredLoa |
207 | 208 | if (encounteredClasses.add(clazz)) { |
208 | 209 | LOGGER.log(Level.FINER, "found {0}", clazz.getName()); |
209 | 210 | Introspector.flushFromCaches(clazz); |
| 211 | + cleanUpClassInfoCache(clazz); |
210 | 212 | cleanUpGlobalClassSet(clazz); |
211 | 213 | cleanUpClassHelperCache(clazz); |
212 | 214 | cleanUpObjectStreamClassCaches(clazz); |
@@ -267,6 +269,45 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws |
267 | 269 | } |
268 | 270 | } |
269 | 271 |
|
| 272 | + private static void cleanUpClassInfoCache(Class<?> clazz) { |
| 273 | + JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM(); |
| 274 | + if (current.isNewerThan(new JavaSpecificationVersion("1.8")) |
| 275 | + && current.isOlderThan(new JavaSpecificationVersion("16"))) { |
| 276 | + try { |
| 277 | + // TODO Work around JDK-8231454. |
| 278 | + Class<?> classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo"); |
| 279 | + Field cacheF = classInfoC.getDeclaredField("CACHE"); |
| 280 | + try { |
| 281 | + cacheF.setAccessible(true); |
| 282 | + } catch (RuntimeException e) { // TODO Java 9+ InaccessibleObjectException |
| 283 | + /* |
| 284 | + * Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". |
| 285 | + * Until core adds this to its --add-opens configuration, and until that core |
| 286 | + * change is widely adopted, avoid unnecessary log spam and return early. |
| 287 | + */ |
| 288 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 289 | + LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); |
| 290 | + } |
| 291 | + return; |
| 292 | + } |
| 293 | + Object cache = cacheF.get(null); |
| 294 | + Class<?> cacheC = Class.forName("com.sun.beans.util.Cache"); |
| 295 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 296 | + LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE."); |
| 297 | + } |
| 298 | + Method removeM = cacheC.getMethod("remove", Object.class); |
| 299 | + removeM.invoke(cache, clazz); |
| 300 | + } catch (ReflectiveOperationException e) { |
| 301 | + /* |
| 302 | + * Should never happen, but if it does, ensure the failure is isolated to this |
| 303 | + * method and does not prevent other cleanup logic from executing. |
| 304 | + */ |
| 305 | + LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); |
| 306 | + } |
| 307 | + } |
| 308 | + } |
| 309 | + |
| 310 | + |
270 | 311 | private static void cleanUpGlobalClassSet(@NonNull Class<?> clazz) throws Exception { |
271 | 312 | Class<?> classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there |
272 | 313 | Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet"); |
@@ -314,15 +355,18 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class<?> clazz) thro |
314 | 355 | for (String cacheFName : new String[] {"localDescs", "reflectors"}) { |
315 | 356 | Field cacheF = cachesC.getDeclaredField(cacheFName); |
316 | 357 | cacheF.setAccessible(true); |
317 | | - ConcurrentMap<Reference<Class<?>>, ?> cache = (ConcurrentMap) cacheF.get(null); |
318 | | - Iterator<? extends Map.Entry<Reference<Class<?>>, ?>> iterator = cache.entrySet().iterator(); |
319 | | - while (iterator.hasNext()) { |
320 | | - if (iterator.next().getKey().get() == clazz) { |
321 | | - iterator.remove(); |
322 | | - if (LOGGER.isLoggable(Level.FINER)) { |
323 | | - LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName}); |
| 358 | + Object cache = cacheF.get(null); |
| 359 | + if (cache instanceof ConcurrentMap) { |
| 360 | + // Prior to JDK-8277072 |
| 361 | + Iterator<? extends Map.Entry<Reference<Class<?>>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator(); |
| 362 | + while (iterator.hasNext()) { |
| 363 | + if (iterator.next().getKey().get() == clazz) { |
| 364 | + iterator.remove(); |
| 365 | + if (LOGGER.isLoggable(Level.FINER)) { |
| 366 | + LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName}); |
| 367 | + } |
| 368 | + break; |
324 | 369 | } |
325 | | - break; |
326 | 370 | } |
327 | 371 | } |
328 | 372 | } |
|
0 commit comments