Skip to content

Commit ebf08fd

Browse files
author
Dmytro Ukhlov
committed
Move common caching classloader functionality to the CachingClassLoader class
Use it for UberClassLoder, as a result - UberClassLoder caches all results of loadClass function (not only findClass)
1 parent c781b99 commit ebf08fd

File tree

2 files changed

+66
-18
lines changed

2 files changed

+66
-18
lines changed

core/src/main/java/hudson/PluginManager.java

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
import hudson.security.ACLContext;
5959
import hudson.security.Permission;
6060
import hudson.security.PermissionScope;
61+
import hudson.util.CachingClassLoader;
6162
import hudson.util.CyclicGraphDetector;
6263
import hudson.util.CyclicGraphDetector.CycleDetectedException;
63-
import hudson.util.DelegatingClassLoader;
6464
import hudson.util.ExistenceCheckingClassLoader;
6565
import hudson.util.FormValidation;
6666
import hudson.util.PersistedList;
@@ -108,13 +108,11 @@
108108
import java.util.Locale;
109109
import java.util.Map;
110110
import java.util.Objects;
111-
import java.util.Optional;
112111
import java.util.ServiceLoader;
113112
import java.util.Set;
114113
import java.util.TreeMap;
115114
import java.util.UUID;
116115
import java.util.concurrent.ConcurrentHashMap;
117-
import java.util.concurrent.ConcurrentMap;
118116
import java.util.concurrent.CopyOnWriteArrayList;
119117
import java.util.concurrent.Future;
120118
import java.util.function.Supplier;
@@ -2402,12 +2400,9 @@ public <T> T of(String key, Class<T> type, Supplier<T> func) {
24022400
/**
24032401
* {@link ClassLoader} that can see all plugins.
24042402
*/
2405-
public static final class UberClassLoader extends DelegatingClassLoader {
2403+
public static final class UberClassLoader extends CachingClassLoader {
24062404
private final List<PluginWrapper> activePlugins;
24072405

2408-
/** Cache of loaded, or known to be unloadable, classes. */
2409-
private final ConcurrentMap<String, Optional<Class<?>>> loaded = new ConcurrentHashMap<>();
2410-
24112406
/**
24122407
* The servlet container's {@link ClassLoader} (the parent of Jenkins core) is
24132408
* parallel-capable and maintains its own growing {@link Map} of {@link
@@ -2426,29 +2421,29 @@ public UberClassLoader(List<PluginWrapper> activePlugins) {
24262421
}
24272422

24282423
@Override
2429-
protected Class<?> findClass(String name) throws ClassNotFoundException {
2424+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
24302425
for (String namePrefixToSkip : CLASS_PREFIXES_TO_SKIP) {
24312426
if (name.startsWith(namePrefixToSkip)) {
24322427
throw new ClassNotFoundException("ignoring " + name);
24332428
}
24342429
}
2435-
return loaded.computeIfAbsent(name, this::computeValue).orElseThrow(() -> new ClassNotFoundException(name));
2430+
return super.loadClass(name, resolve);
24362431
}
24372432

2438-
private Optional<Class<?>> computeValue(String name) {
2433+
@Override
2434+
protected Class<?> findClass(String name) throws ClassNotFoundException {
24392435
for (PluginWrapper p : activePlugins) {
24402436
try {
24412437
if (FAST_LOOKUP) {
2442-
return Optional.of(ClassLoaderReflectionToolkit.loadClass(p.classLoader, name));
2438+
return ClassLoaderReflectionToolkit.loadClass(p.classLoader, name);
24432439
} else {
2444-
return Optional.of(p.classLoader.loadClass(name));
2440+
return p.classLoader.loadClass(name);
24452441
}
24462442
} catch (ClassNotFoundException e) {
24472443
// Not found. Try the next class loader.
24482444
}
24492445
}
2450-
// Not found in any of the class loaders. Delegate.
2451-
return Optional.empty();
2446+
throw new ClassNotFoundException(name);
24522447
}
24532448

24542449
@Override
@@ -2480,10 +2475,6 @@ protected Enumeration<URL> findResources(String name) throws IOException {
24802475
return Collections.enumeration(resources);
24812476
}
24822477

2483-
void clearCacheMisses() {
2484-
loaded.values().removeIf(Optional::isEmpty);
2485-
}
2486-
24872478
@Override
24882479
public String toString() {
24892480
// only for debugging purpose
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package hudson.util;
2+
3+
import org.kohsuke.accmod.Restricted;
4+
import org.kohsuke.accmod.restrictions.NoExternalUse;
5+
6+
import java.util.Optional;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
9+
/**
10+
*
11+
* ClassLoader with internal caching of class loading results.
12+
*
13+
* <p>
14+
* Caches both successful and failed class lookups to avoid redundant delegation
15+
* and repeated class resolution attempts. Designed for performance optimization
16+
* in systems that repeatedly query class presence (e.g., plugin environments,
17+
* reflective loading, optional dependencies).
18+
* </p>
19+
*
20+
* Useful for classloaders that have heavy-weight loadClass() implementations
21+
*
22+
* @author Dmytro Ukhlov
23+
*/
24+
@Restricted(NoExternalUse.class)
25+
public class CachingClassLoader extends DelegatingClassLoader {
26+
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
27+
28+
public CachingClassLoader(String name, ClassLoader parent) {
29+
super(name, parent);
30+
}
31+
32+
@Override
33+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
34+
Object classOrEmpty = cache.computeIfAbsent(name, key -> {
35+
try {
36+
return super.loadClass(name, false);
37+
} catch (ClassNotFoundException e) {
38+
// Not found.
39+
return Optional.empty();
40+
}
41+
});
42+
43+
if (classOrEmpty == Optional.empty()) {
44+
throw new ClassNotFoundException(name);
45+
}
46+
47+
Class<?> clazz = (Class<?>) classOrEmpty;
48+
if (resolve) {
49+
resolveClass(clazz);
50+
}
51+
return clazz;
52+
}
53+
54+
public void clearCacheMisses() {
55+
cache.values().removeIf(v -> v == Optional.empty());
56+
}
57+
}

0 commit comments

Comments
 (0)