Skip to content

Commit b0aa4a3

Browse files
Dmytro Ukhlovdukhlov
authored andcommitted
JENKINS-75675: Refactor classloader logic in order to reduce memory consumption
Override URLClassloader2.getClassLoadingLock to use lock objects that can be GCed when not needed, Wrap WebAppClassLoader with CheckingExistenceClassLoader to avoid missing loadClass() calls not to crate unnecessary persistent lock object, Refactor delegating classloaders not to use locking: * DelegatingClassloader - non-locking base implementation and use it for classloaders which delegate functionality to other classloaders, * CheckingExistenceClassLoader - check that class exists before loadClass() calls.
1 parent d1fa466 commit b0aa4a3

File tree

6 files changed

+120
-17
lines changed

6 files changed

+120
-17
lines changed

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import hudson.model.Hudson;
3434
import hudson.util.CyclicGraphDetector;
3535
import hudson.util.CyclicGraphDetector.CycleDetectedException;
36+
import hudson.util.DelegatingClassLoader;
3637
import hudson.util.IOUtils;
3738
import hudson.util.MaskingClassLoader;
3839
import java.io.File;
@@ -559,7 +560,7 @@ private static void unzipExceptClasses(File archive, File destDir, Project prj)
559560
/**
560561
* Used to load classes from dependency plugins.
561562
*/
562-
static final class DependencyClassLoader extends ClassLoader {
563+
static final class DependencyClassLoader extends DelegatingClassLoader {
563564
/**
564565
* This classloader is created for this plugin. Useful during debugging.
565566
*/
@@ -574,10 +575,6 @@ static final class DependencyClassLoader extends ClassLoader {
574575
*/
575576
private volatile List<PluginWrapper> transitiveDependencies;
576577

577-
static {
578-
registerAsParallelCapable();
579-
}
580-
581578
DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies, PluginManager pluginManager) {
582579
super("dependency ClassLoader for " + archive.getPath(), parent);
583580
this._for = archive;

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@
5858
import hudson.security.ACLContext;
5959
import hudson.security.Permission;
6060
import hudson.security.PermissionScope;
61+
import hudson.util.CheckingExistenceClassLoader;
6162
import hudson.util.CyclicGraphDetector;
6263
import hudson.util.CyclicGraphDetector.CycleDetectedException;
64+
import hudson.util.DelegatingClassLoader;
6365
import hudson.util.FormValidation;
6466
import hudson.util.PersistedList;
6567
import hudson.util.Retrier;
@@ -2392,18 +2394,14 @@ public <T> T of(String key, Class<T> type, Supplier<T> func) {
23922394
/**
23932395
* {@link ClassLoader} that can see all plugins.
23942396
*/
2395-
public static final class UberClassLoader extends ClassLoader {
2397+
public static final class UberClassLoader extends DelegatingClassLoader {
23962398
private final List<PluginWrapper> activePlugins;
23972399

23982400
/** Cache of loaded, or known to be unloadable, classes. */
23992401
private final ConcurrentMap<String, Optional<Class<?>>> loaded = new ConcurrentHashMap<>();
24002402

2401-
static {
2402-
registerAsParallelCapable();
2403-
}
2404-
24052403
public UberClassLoader(List<PluginWrapper> activePlugins) {
2406-
super("UberClassLoader", PluginManager.class.getClassLoader());
2404+
super("UberClassLoader", new CheckingExistenceClassLoader(PluginManager.class.getClassLoader()));
24072405
this.activePlugins = activePlugins;
24082406
}
24092407

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package hudson.util;
2+
3+
/**
4+
* A class loader that verifies the existence of a class resource before attempting to load the class.
5+
* <p>
6+
* This implementation overrides {@link #loadClass(String, boolean)} and uses {@link #getResource(String)}
7+
* to check whether the corresponding <code>.class</code> file is available in the classpath.
8+
* If the resource is not found, a {@link ClassNotFoundException} is thrown immediately.
9+
* </p>
10+
*
11+
* <p>This approach can be useful in environments where conditional class availability
12+
* must be verified without triggering side effects from the loading process.</p>
13+
*
14+
* @see ClassLoader
15+
* @see #getResource(String)
16+
*/
17+
public class CheckingExistenceClassLoader extends DelegatingClassLoader {
18+
19+
public CheckingExistenceClassLoader(String name, ClassLoader parent) {
20+
super(name, parent);
21+
}
22+
23+
public CheckingExistenceClassLoader(ClassLoader parent) {
24+
super(parent);
25+
}
26+
27+
@Override
28+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
29+
if (getResource(name.replace('.', '/') + ".class") == null &&
30+
!name.equals("java.lang.$JaCoCo")) { // Add support for loading of JaCoCo dynamic instrumentation classes
31+
throw new ClassNotFoundException(name);
32+
}
33+
34+
return super.loadClass(name, resolve);
35+
}
36+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package hudson.util;
2+
3+
/**
4+
* A {@link ClassLoader} that does not define any classes itself but delegates class loading
5+
* to other class loaders. It first attempts to load classes via its {@code getParent()} class loader,
6+
* then falls back to {@code findClass} to allow for custom delegation logic.
7+
* <p>
8+
* This class can also serve as the parent class loader for other class loaders that follow
9+
* the standard delegation model.
10+
*
11+
* @author Dmytro Ukhlov
12+
*/
13+
public class DelegatingClassLoader extends ClassLoader {
14+
protected DelegatingClassLoader(String name, ClassLoader parent) {
15+
super(name, parent);
16+
}
17+
18+
public DelegatingClassLoader(ClassLoader parent) {
19+
super(parent);
20+
}
21+
22+
/**
23+
* Overrides base implementation to skip unnecessary synchronization
24+
*
25+
* @param name
26+
* The <a href="#binary-name">binary name</a> of the class
27+
*
28+
* @param resolve
29+
* If {@code true} then resolve the class
30+
*
31+
* @return The resulting {@code Class} object
32+
*
33+
* @throws ClassNotFoundException
34+
* If the class could not be found
35+
*/
36+
@Override
37+
protected Class<?> loadClass(String name, boolean resolve)
38+
throws ClassNotFoundException
39+
{
40+
Class<?> c = null;
41+
try {
42+
if (getParent() != null) {
43+
c = getParent().loadClass(name);
44+
}
45+
} catch (ClassNotFoundException e) {
46+
// ClassNotFoundException thrown if class not found
47+
// from the non-null parent class loader
48+
}
49+
50+
if (c == null) {
51+
// If still not found, then invoke findClass in order
52+
// to find the class.
53+
c = findClass(name);
54+
}
55+
56+
if (resolve) {
57+
resolveClass(c);
58+
}
59+
return c;
60+
}
61+
}

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,14 @@
4242
*
4343
* @author Kohsuke Kawaguchi
4444
*/
45-
public class MaskingClassLoader extends ClassLoader {
45+
public class MaskingClassLoader extends DelegatingClassLoader {
4646
/**
4747
* Prefix of the packages that should be hidden.
4848
*/
4949
private final List<String> masksClasses;
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
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,23 @@ public Class<?> findLoadedClass2(String name) {
6969
return super.findLoadedClass(name);
7070
}
7171

72+
/**
73+
* Use String.intern() to create ClassLoader/className unique lock object which can be GCed
74+
*
75+
* @param className
76+
* The name of the to-be-loaded class
77+
*
78+
* @return lock object, unique per classloader/classname combination
79+
*/
7280
@Override
7381
public Object getClassLoadingLock(String className) {
74-
return super.getClassLoadingLock(className);
82+
return new StringBuilder(128)
83+
.append(getClass().getSimpleName())
84+
.append("@")
85+
.append(Integer.toHexString(System.identityHashCode(this)))
86+
.append("-loadClassLock:")
87+
.append(className)
88+
.toString()
89+
.intern();
7590
}
7691
}

0 commit comments

Comments
 (0)