55import org .kohsuke .accmod .restrictions .NoExternalUse ;
66
77/**
8- * A {@link ClassLoader} that does not define any classes itself but delegates class loading
9- * to other class loaders. It first attempts to load classes via its {@code getParent()} class loader,
10- * then falls back to {@code findClass} to allow for custom delegation logic.
8+ * A {@link ClassLoader} that does not define any classes itself but delegates class loading to other class loaders to
9+ * avoid the JDK's per-class-name locking and lock retention.
1110 * <p>
12- * This class can also serve as the parent class loader for other class loaders that follow
13- * the standard delegation model.
11+ * This class first attempts to load classes via its {@link ClassLoader#getParent} class loader, then falls back to
12+ * {@link ClassLoader#findClass} to allow for custom delegation logic.
13+ * <p>
14+ * In a parallel-capable {@link ClassLoader{, the JDK maintains a per-name lock object indefinitely. In Jenkins, many
15+ * class loading misses across many loaders can accumulate hundreds of thousands of such locks, retaining significant
16+ * memory. This loader never defines classes and bypasses {@link ClassLoader}'s default {@code loadClass} locking; it
17+ * delegates to the parent first and then to {@code findClass} for custom delegation.
18+ * <p>
19+ * The actual defining loader (parent or a delegate) still performs the necessary synchronization and class definition.
20+ * A runtime guard ({@link #verify}) throws if this loader ever becomes the defining loader.
21+ * <p>
22+ * Subclasses must not call {@code defineClass}; implement delegation in {@code findClass} if needed and do not mark
23+ * subclasses as parallel-capable.
1424 *
1525 * @author Dmytro Ukhlov
1626 */
@@ -25,18 +35,14 @@ public DelegatingClassLoader(ClassLoader parent) {
2535 }
2636
2737 /**
28- * Overrides base implementation to skip unnecessary synchronization
29- *
30- * @param name
31- * The <a href="#binary-name">binary name</a> of the class
32- *
33- * @param resolve
34- * If {@code true} then resolve the class
38+ * Parent-first delegation without synchronizing on {@link #getClassLoadingLock(String)}. This
39+ * prevents creation/retention of per-name lock objects in a loader that does not define
40+ * classes. The defining loader downstream still serializes class definition as required.
3541 *
36- * @return The resulting {@code Class} object
37- *
38- * @throws ClassNotFoundException
39- * If the class could not be found
42+ * @param name The binary name of the class
43+ * @param resolve If {@code true} then resolve the class
44+ * @return The resulting {@link Class} object
45+ * @throws ClassNotFoundException If the class could not be found
4046 */
4147 @ Override
4248 protected Class <?> loadClass (String name , boolean resolve ) throws ClassNotFoundException {
@@ -61,6 +67,12 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
6167 return c ;
6268 }
6369
70+ /**
71+ * Safety check to ensure this delegating loader never becomes the defining loader.
72+ *
73+ * <p>Fails fast if a subclass erroneously defines a class here, which would violate the
74+ * delegation-only contract and could reintroduce locking/retention issues.
75+ */
6476 protected Class <?> verify (Class <?> clazz ) {
6577 if (clazz .getClassLoader () == this ) {
6678 throw new IllegalStateException ("DelegatingClassLoader must not be the defining loader: " + clazz .getName ());
0 commit comments