Skip to content

Commit c04a84c

Browse files
author
Dmytro Ukhlov
committed
JENKINS-75675: Refactor classloader logic in order to reduce memory consumption
1 parent 86a6fd8 commit c04a84c

File tree

5 files changed

+94
-45
lines changed

5 files changed

+94
-45
lines changed

core/src/main/java/hudson/ClassicPluginStrategy.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -574,10 +574,6 @@ static final class DependencyClassLoader extends ClassLoader {
574574
*/
575575
private volatile List<PluginWrapper> transitiveDependencies;
576576

577-
static {
578-
registerAsParallelCapable();
579-
}
580-
581577
DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies, PluginManager pluginManager) {
582578
super("dependency ClassLoader for " + archive.getPath(), parent);
583579
this._for = archive;
@@ -627,7 +623,13 @@ protected List<PluginWrapper> getEdges(PluginWrapper pw) {
627623
}
628624

629625
@Override
630-
protected Class<?> findClass(String name) throws ClassNotFoundException {
626+
protected Class<?> loadClass(String name, boolean resolved) throws ClassNotFoundException {
627+
try {
628+
return getParent().loadClass(name);
629+
} catch (ClassNotFoundException ignored) {
630+
// OK, try next
631+
}
632+
631633
if (PluginManager.FAST_LOOKUP) {
632634
for (PluginWrapper pw : getTransitiveDependencies()) {
633635
try {
@@ -655,20 +657,25 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
655657
@Override
656658
@SuppressFBWarnings(value = "DMI_COLLECTION_OF_URLS",
657659
justification = "Should not produce network overheads since the URL is local. JENKINS-53793 is a follow-up")
658-
protected Enumeration<URL> findResources(String name) throws IOException {
660+
public Enumeration<URL> getResources(String name) throws IOException {
659661
HashSet<URL> result = new HashSet<>();
660662

663+
Enumeration<URL> urls = getParent().getResources(name);
664+
while (urls.hasMoreElements()) {
665+
result.add(urls.nextElement());
666+
}
667+
661668
if (PluginManager.FAST_LOOKUP) {
662669
for (PluginWrapper pw : getTransitiveDependencies()) {
663-
Enumeration<URL> urls = ClassLoaderReflectionToolkit._findResources(pw.classLoader, name);
670+
urls = ClassLoaderReflectionToolkit._findResources(pw.classLoader, name);
664671
while (urls != null && urls.hasMoreElements())
665672
result.add(urls.nextElement());
666673
}
667674
} else {
668675
for (Dependency dep : dependencies) {
669676
PluginWrapper p = pluginManager.getPlugin(dep.shortName);
670677
if (p != null) {
671-
Enumeration<URL> urls = p.classLoader.getResources(name);
678+
urls = p.classLoader.getResources(name);
672679
while (urls != null && urls.hasMoreElements())
673680
result.add(urls.nextElement());
674681
}
@@ -679,17 +686,22 @@ protected Enumeration<URL> findResources(String name) throws IOException {
679686
}
680687

681688
@Override
682-
protected URL findResource(String name) {
689+
public URL getResource(String name) {
690+
URL url = getParent().getResource(name);
691+
if (url != null) {
692+
return url;
693+
}
694+
683695
if (PluginManager.FAST_LOOKUP) {
684696
for (PluginWrapper pw : getTransitiveDependencies()) {
685-
URL url = ClassLoaderReflectionToolkit._findResource(pw.classLoader, name);
697+
url = ClassLoaderReflectionToolkit._findResource(pw.classLoader, name);
686698
if (url != null) return url;
687699
}
688700
} else {
689701
for (Dependency dep : dependencies) {
690702
PluginWrapper p = pluginManager.getPlugin(dep.shortName);
691703
if (p != null) {
692-
URL url = p.classLoader.getResource(name);
704+
url = p.classLoader.getResource(name);
693705
if (url != null)
694706
return url;
695707
}

core/src/main/java/hudson/PluginFirstClassLoader2.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@
2121
*/
2222
@Restricted(NoExternalUse.class)
2323
public class PluginFirstClassLoader2 extends URLClassLoader2 {
24-
static {
25-
registerAsParallelCapable();
26-
}
27-
2824

2925
public PluginFirstClassLoader2(String name, @NonNull URL[] urls, @NonNull ClassLoader parent) {
3026
super(name, Objects.requireNonNull(urls), Objects.requireNonNull(parent));

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

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@
106106
import java.util.Locale;
107107
import java.util.Map;
108108
import java.util.Objects;
109-
import java.util.Optional;
110109
import java.util.ServiceLoader;
111110
import java.util.Set;
112111
import java.util.TreeMap;
@@ -2397,48 +2396,72 @@ public <T> T of(String key, Class<T> type, Supplier<T> func) {
23972396
* {@link ClassLoader} that can see all plugins.
23982397
*/
23992398
public static final class UberClassLoader extends ClassLoader {
2399+
private static final int CLASS_LOAD_MISSES_CACHE_SIZE = 1000;
2400+
24002401
private final List<PluginWrapper> activePlugins;
24012402

2402-
/** Cache of loaded, or known to be unloadable, classes. */
2403-
private final ConcurrentMap<String, Optional<Class<?>>> loaded = new ConcurrentHashMap<>();
2403+
/** Cache of loaded classes. */
2404+
private final ConcurrentMap<String, Class<?>> loaded = new ConcurrentHashMap<>();
24042405

2405-
static {
2406-
registerAsParallelCapable();
2407-
}
2406+
/** Cache of classes known to be unloadable */
2407+
private volatile Map<String, String> classLoadMissesCache;
24082408

24092409
public UberClassLoader(List<PluginWrapper> activePlugins) {
24102410
super("UberClassLoader", PluginManager.class.getClassLoader());
24112411
this.activePlugins = activePlugins;
2412+
clearCacheMisses();
24122413
}
24132414

24142415
@Override
2415-
protected Class<?> findClass(String name) throws ClassNotFoundException {
2416-
if (name.startsWith("SimpleTemplateScript")) { // cf. groovy.text.SimpleTemplateEngine
2416+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
2417+
if (name.startsWith("SimpleTemplateScript") || // cf. groovy.text.SimpleTemplateEngine
2418+
name.startsWith("groovy.tmp.") ||
2419+
classLoadMissesCache.containsKey(name)) {
24172420
throw new ClassNotFoundException("ignoring " + name);
24182421
}
2419-
return loaded.computeIfAbsent(name, this::computeValue).orElseThrow(() -> new ClassNotFoundException(name));
2422+
2423+
Class<?> clazz = loaded.computeIfAbsent(name, this::computeValue);
2424+
if (clazz == null) {
2425+
classLoadMissesCache.put(name, name);
2426+
throw new ClassNotFoundException(name);
2427+
}
2428+
return clazz;
24202429
}
24212430

2422-
private Optional<Class<?>> computeValue(String name) {
2431+
private Class<?> computeValue(String name) {
2432+
if (!name.equals("java.lang.$JaCoCo") && // Add support for loading of JaCoCo dynamic instrumentation classes
2433+
getResource(name.replace('.', '/') + ".class") == null) {
2434+
return null;
2435+
}
2436+
2437+
try {
2438+
return getParent().loadClass(name);
2439+
} catch (ClassNotFoundException e) {
2440+
// Not found. Try the next class loader.
2441+
}
2442+
24232443
for (PluginWrapper p : activePlugins) {
24242444
try {
24252445
if (FAST_LOOKUP) {
2426-
return Optional.of(ClassLoaderReflectionToolkit.loadClass(p.classLoader, name));
2446+
return ClassLoaderReflectionToolkit.loadClass(p.classLoader, name);
24272447
} else {
2428-
return Optional.of(p.classLoader.loadClass(name));
2448+
return p.classLoader.loadClass(name);
24292449
}
24302450
} catch (ClassNotFoundException e) {
24312451
// Not found. Try the next class loader.
24322452
}
24332453
}
2434-
// Not found in any of the class loaders. Delegate.
2435-
return Optional.empty();
2454+
return null;
24362455
}
24372456

24382457
@Override
2439-
protected URL findResource(String name) {
2458+
public URL getResource(String name) {
2459+
URL url = getParent().getResource(name);
2460+
if (url != null) {
2461+
return url;
2462+
}
2463+
24402464
for (PluginWrapper p : activePlugins) {
2441-
URL url;
24422465
if (FAST_LOOKUP) {
24432466
url = ClassLoaderReflectionToolkit._findResource(p.classLoader, name);
24442467
} else {
@@ -2452,8 +2475,10 @@ protected URL findResource(String name) {
24522475
}
24532476

24542477
@Override
2455-
protected Enumeration<URL> findResources(String name) throws IOException {
2478+
public Enumeration<URL> getResources(String name) throws IOException {
24562479
List<URL> resources = new ArrayList<>();
2480+
resources.addAll(Collections.list(getParent().getResources(name)));
2481+
24572482
for (PluginWrapper p : activePlugins) {
24582483
if (FAST_LOOKUP) {
24592484
resources.addAll(Collections.list(ClassLoaderReflectionToolkit._findResources(p.classLoader, name)));
@@ -2465,7 +2490,11 @@ protected Enumeration<URL> findResources(String name) throws IOException {
24652490
}
24662491

24672492
void clearCacheMisses() {
2468-
loaded.values().removeIf(Optional::isEmpty);
2493+
classLoadMissesCache = Collections.synchronizedMap(new LinkedHashMap<>() {
2494+
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
2495+
return size() > CLASS_LOAD_MISSES_CACHE_SIZE;
2496+
}
2497+
});
24692498
}
24702499

24712500
@Override

core/src/main/java/hudson/util/MaskingClassLoader.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ public class MaskingClassLoader extends ClassLoader {
5050

5151
private final List<String> masksResources;
5252

53-
static {
54-
registerAsParallelCapable();
55-
}
56-
5753
public MaskingClassLoader(ClassLoader parent, String... masks) {
5854
this(parent, Arrays.asList(masks));
5955
}
@@ -76,21 +72,21 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
7672
throw new ClassNotFoundException();
7773
}
7874

79-
return super.loadClass(name, resolve);
75+
return getParent().loadClass(name);
8076
}
8177

8278
@Override
8379
public URL getResource(String name) {
8480
if (isMasked(name)) return null;
8581

86-
return super.getResource(name);
82+
return getParent().getResource(name);
8783
}
8884

8985
@Override
9086
public Enumeration<URL> getResources(String name) throws IOException {
9187
if (isMasked(name)) return Collections.emptyEnumeration();
9288

93-
return super.getResources(name);
89+
return getParent().getResources(name);
9490
}
9591

9692
private boolean isMasked(String name) {

core/src/main/java/jenkins/util/URLClassLoader2.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package jenkins.util;
22

3+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
4+
import java.lang.ref.WeakReference;
35
import java.net.URL;
46
import java.net.URLClassLoader;
7+
import java.util.WeakHashMap;
58
import jenkins.ClassLoaderReflectionToolkit;
69
import org.kohsuke.accmod.Restricted;
710
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -12,10 +15,7 @@
1215
*/
1316
@Restricted(NoExternalUse.class)
1417
public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoader {
15-
16-
static {
17-
registerAsParallelCapable();
18-
}
18+
private final WeakHashMap<String, WeakReference<String>> parallelLockMap2 = new WeakHashMap<>();
1919

2020
/**
2121
* @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[])}
@@ -70,7 +70,23 @@ public Class<?> findLoadedClass2(String name) {
7070
}
7171

7272
@Override
73+
@SuppressFBWarnings(value = "DM_STRING_CTOR", justification = "Need to have a lock object which can be GCed and " +
74+
"can be retrieved by className. Solution: create new String(className) object, use it as lockObject " +
75+
"and store in WeakHashMap as a key and weak referenced value. It can be retrieved by className because " +
76+
"className.hashcode() == new String(className).hashcode(), className.equals(new String(className)) == true")
7377
public Object getClassLoadingLock(String className) {
74-
return super.getClassLoadingLock(className);
78+
synchronized (parallelLockMap2) {
79+
WeakReference<String> lockObjectWeakRef = parallelLockMap2.get(className);
80+
String lockObject = null;
81+
if (lockObjectWeakRef != null) {
82+
lockObject = lockObjectWeakRef.get();
83+
}
84+
if (lockObject != null) {
85+
return lockObject;
86+
}
87+
lockObject = new String(className);
88+
parallelLockMap2.put(lockObject, new WeakReference<>(lockObject));
89+
return lockObject;
90+
}
7591
}
7692
}

0 commit comments

Comments
 (0)