Skip to content

Commit fc1fa28

Browse files
author
Dmytro Ukhlov
committed
JENKINS-75675: Refactor classloader logic in order to reduce memory consumption
1 parent 648ad34 commit fc1fa28

File tree

6 files changed

+66
-25
lines changed

6 files changed

+66
-25
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/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: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import hudson.security.PermissionScope;
6161
import hudson.util.CyclicGraphDetector;
6262
import hudson.util.CyclicGraphDetector.CycleDetectedException;
63+
import hudson.util.DelegatingClassLoader;
6364
import hudson.util.FormValidation;
6465
import hudson.util.PersistedList;
6566
import hudson.util.Retrier;
@@ -2396,30 +2397,32 @@ public <T> T of(String key, Class<T> type, Supplier<T> func) {
23962397
/**
23972398
* {@link ClassLoader} that can see all plugins.
23982399
*/
2399-
public static final class UberClassLoader extends ClassLoader {
2400+
public static final class UberClassLoader extends DelegatingClassLoader {
24002401
private final List<PluginWrapper> activePlugins;
24012402

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

2405-
static {
2406-
registerAsParallelCapable();
2407-
}
2408-
24092406
public UberClassLoader(List<PluginWrapper> activePlugins) {
24102407
super("UberClassLoader", PluginManager.class.getClassLoader());
24112408
this.activePlugins = activePlugins;
24122409
}
24132410

24142411
@Override
24152412
protected Class<?> findClass(String name) throws ClassNotFoundException {
2416-
if (name.startsWith("SimpleTemplateScript")) { // cf. groovy.text.SimpleTemplateEngine
2413+
if (name.startsWith("SimpleTemplateScript") || // cf. groovy.text.SimpleTemplateEngine
2414+
name.startsWith("groovy.tmp.")) {
24172415
throw new ClassNotFoundException("ignoring " + name);
24182416
}
24192417
return loaded.computeIfAbsent(name, this::computeValue).orElseThrow(() -> new ClassNotFoundException(name));
24202418
}
24212419

24222420
private Optional<Class<?>> computeValue(String name) {
2421+
if (!name.equals("java.lang.$JaCoCo") && // Add support for loading of JaCoCo dynamic instrumentation classes
2422+
getResource(name.replace('.', '/') + ".class") == null) {
2423+
return Optional.empty();
2424+
}
2425+
24232426
for (PluginWrapper p : activePlugins) {
24242427
try {
24252428
if (FAST_LOOKUP) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
/**
19+
* Overrides base implementation to skip unnecessary synchronization
20+
*
21+
* @param name
22+
* The <a href="#binary-name">binary name</a> of the class
23+
*
24+
* @param resolve
25+
* If {@code true} then resolve the class
26+
*
27+
* @return The resulting {@code Class} object
28+
*
29+
* @throws ClassNotFoundException
30+
* If the class could not be found
31+
*/
32+
@Override
33+
protected Class<?> loadClass(String name, boolean resolve)
34+
throws ClassNotFoundException
35+
{
36+
Class<?> c = null;
37+
try {
38+
if (getParent() != null) {
39+
c = Class.forName(name, resolve, getParent());
40+
}
41+
} catch (ClassNotFoundException e) {
42+
// ClassNotFoundException thrown if class not found
43+
// from the non-null parent class loader
44+
}
45+
46+
if (c == null) {
47+
// If still not found, then invoke findClass in order
48+
// to find the class.
49+
c = findClass(name);
50+
}
51+
52+
return c;
53+
}
54+
}

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: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@
1212
*/
1313
@Restricted(NoExternalUse.class)
1414
public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoader {
15-
16-
static {
17-
registerAsParallelCapable();
18-
}
19-
2015
/**
2116
* @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[])}
2217
*/

0 commit comments

Comments
 (0)