Move common caching class loading functionality to the separate class#10979
Merged
krisstern merged 1 commit intojenkinsci:masterfrom Sep 2, 2025
Merged
Conversation
495fb2f to
a58340f
Compare
…er class Use it for UberClassLoder, as a result - UberClassLoder caches all results of loadClass function (not only findClass)
a58340f to
5d9b429
Compare
jeromepochat
approved these changes
Aug 28, 2025
Contributor
jeromepochat
left a comment
There was a problem hiding this comment.
Hi! This refactor makes total sense, encouraging single-responsibility principle. Thanks!
I just left minor suggestion about Optional usage, feel free to ignore!
Comment on lines
+25
to
+55
| private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>(); | ||
|
|
||
| public CachingClassLoader(String name, ClassLoader parent) { | ||
| super(name, parent); | ||
| } | ||
|
|
||
| @Override | ||
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | ||
| Object classOrEmpty = cache.computeIfAbsent(name, key -> { | ||
| try { | ||
| return super.loadClass(name, false); | ||
| } catch (ClassNotFoundException e) { | ||
| // Not found. | ||
| return Optional.empty(); | ||
| } | ||
| }); | ||
|
|
||
| if (classOrEmpty == Optional.empty()) { | ||
| throw new ClassNotFoundException(name); | ||
| } | ||
|
|
||
| Class<?> clazz = (Class<?>) classOrEmpty; | ||
| if (resolve) { | ||
| resolveClass(clazz); | ||
| } | ||
| return clazz; | ||
| } | ||
|
|
||
| public void clearCacheMisses() { | ||
| cache.values().removeIf(v -> v == Optional.empty()); | ||
| } |
Contributor
There was a problem hiding this comment.
suggestion: Maybe the using Optional as map value type could make its usage more natural. WDYT?
Suggested change
| private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>(); | |
| public CachingClassLoader(String name, ClassLoader parent) { | |
| super(name, parent); | |
| } | |
| @Override | |
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | |
| Object classOrEmpty = cache.computeIfAbsent(name, key -> { | |
| try { | |
| return super.loadClass(name, false); | |
| } catch (ClassNotFoundException e) { | |
| // Not found. | |
| return Optional.empty(); | |
| } | |
| }); | |
| if (classOrEmpty == Optional.empty()) { | |
| throw new ClassNotFoundException(name); | |
| } | |
| Class<?> clazz = (Class<?>) classOrEmpty; | |
| if (resolve) { | |
| resolveClass(clazz); | |
| } | |
| return clazz; | |
| } | |
| public void clearCacheMisses() { | |
| cache.values().removeIf(v -> v == Optional.empty()); | |
| } | |
| private final ConcurrentHashMap<String, Optional<Class<?>>> cache = new ConcurrentHashMap<>(); | |
| public CachingClassLoader(String name, ClassLoader parent) { | |
| super(name, parent); | |
| } | |
| @Override | |
| protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | |
| Optional<Class<?>> classOrEmpty = cache.computeIfAbsent(name, key -> { | |
| try { | |
| return Optional.of(super.loadClass(name, false)); | |
| } catch (ClassNotFoundException e) { | |
| // Not found. | |
| return Optional.empty(); | |
| } | |
| }); | |
| if (classOrEmpty.isEmpty()) { | |
| throw new ClassNotFoundException(name); | |
| } | |
| Class<?> clazz = classOrEmpty.get(); | |
| if (resolve) { | |
| resolveClass(clazz); | |
| } | |
| return clazz; | |
| } | |
| public void clearCacheMisses() { | |
| cache.values().removeIf(Optional::isEmpty); | |
| } |
Contributor
Author
There was a problem hiding this comment.
Wrapping with Optional consumes a bit more memory, so I decided not to use it. However, I agree it makes the code more natural. Let's wait for further reviews before making a final decision.
timja
approved these changes
Sep 1, 2025
Member
timja
left a comment
There was a problem hiding this comment.
/label ready-for-merge
This PR is now ready for merge, after ~24 hours, we will merge it if there's no negative feedback.
Thanks!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Move common class-loading caching functionality into CachingClassLoader.
Update UberClassLoader to use CachingClassLoader, so now all results of loadClass() (not just findClass()) are cached.
This improves performance, especially because UberClassLoader uses a ExistenceCheckingClassLoader wrapper for the parent class loader, which introduces overhead.
By caching class-loading results for the parent as well, we avoid redundant lookups and reduce that overhead.
Testing done
Manual testing on local setup, jenkins ci gates.
Proposed changelog entries
Proposed changelog category
/label internal
Proposed upgrade guidelines
N/A
Submitter checklist
@Restrictedor have@since TODOJavadocs, as appropriate.@Deprecated(since = "TODO")or@Deprecated(forRemoval = true, since = "TODO"), if applicable.evalto ease future introduction of Content Security Policy (CSP) directives (see documentation).Desired reviewers
@basil, @timja
Before the changes are marked as ready-for-merge:
Maintainer checklist
upgrade-guide-neededlabel is set and there is a Proposed upgrade guidelines section in the pull request title (see example).lts-candidateto be considered (see query).