Repository: jenkinsci/jenkins
File: core/src/main/java/jenkins/util/URLClassLoader2.java (Java)

Code before:
    public void addURL(URL url) {
        super.addURL(url);
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }

    @Override
    public Class<?> findLoadedClass2(String name) {
        return super.findLoadedClass(name);
    }

    /**
     * Replace the JDK's per-name lock map with a GC-collectable lock object.
     *
     * <p>Parallel-capable {@link ClassLoader} implementations keep a distinct lock object per class
     * name indefinitely, which can retain huge maps when there are many misses. Returning an
     * interned {@link String} keyed by this loader and the class name preserves mutual exclusion
     * for a given (loader, name) pair but allows the JVM to reclaim the lock when no longer
     * referenced. Interned Strings are heap objects and GC-eligible on modern JDKs (7+).
     *
     * @param className the binary name of the class being loaded (must not be null)
     * @return a lock object unique to this classloader/class pair
     */
    @Override
    public Object getClassLoadingLock(String className) {
        Objects.requireNonNull(className);
        return (lockObjectPrefixName + className).intern();
    }
}


Review comment (suggestion):
I almost forgot: can you please update this comment to refer to the upstream JDK issue? Any deviation from upstream in `URLClassLoader2` is a temporary workaround, but based on the discussion in this thread there is a sound justification. So please explain that this code should be removed once the upstream issue (with a link) is fixed.

Code after:
    public void addURL(URL url) {
        super.addURL(url);
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        return super.findClass(name);
    }

    @Override
    public Class<?> findLoadedClass2(String name) {
        return super.findLoadedClass(name);
    }

    /**
     * Replace the JDK's per-name lock map with a GC-collectable lock object. This is a workaround
     * for JDK-8005233. When JDK-8005233 is resolved, this should be deleted. See also the
     * discussion in <a
     * href="https://mail.openjdk.org/pipermail/core-libs-dev/2025-May/146392.html">this OpenJDK
     * thread</a>.
     *
     * <p>Parallel-capable {@link ClassLoader} implementations keep a distinct lock object per class
     * name indefinitely, which can retain huge maps when there are many misses. Returning an
     * interned {@link String} keyed by this loader and the class name preserves mutual exclusion
     * for a given (loader, name) pair but allows the JVM to reclaim the lock when no longer
     * referenced. Interned Strings are heap objects and GC-eligible on modern JDKs (7+).
     *
     * @param className the binary name of the class being loaded (must not be null)
     * @return a lock object unique to this classloader/class pair
     */
    @Override
    public Object getClassLoadingLock(String className) {
        Objects.requireNonNull(className);
        return (lockObjectPrefixName + className).intern();
    }
}