find classloader for type by class package · lk2020coder/dd-trace-java@edc2e37 · GitHub
Skip to content

Commit edc2e37

Browse files
find classloader for type by class package
1 parent 0471f3f commit edc2e37

4 files changed

Lines changed: 158 additions & 41 deletions

File tree

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/Agent.java

Lines changed: 19 additions & 3 deletions

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/DatadogClassLoader.java

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,42 @@
66
import lombok.extern.slf4j.Slf4j;
77

88
/**
9-
* Classloader used to run the core datadog agent.
10-
*
11-
* <p>It is built around the concept of a jar inside another jar. This classloader loads the files
12-
* of the internal jar to load classes and resources.
9+
* A classloader which maintains a package index of isolated delegate classloaders, and delegates
10+
* class loads according to package. Loads classes itself according to package, but delegates
11+
* upwards when a class cannot be loaded.
1312
*/
1413
@Slf4j
1514
public class DatadogClassLoader extends URLClassLoader {
1615
static {
1716
ClassLoader.registerAsParallelCapable();
1817
}
1918

19+
protected final InternalJarURLHandler internalJarURLHandler;
20+
2021
// Calling java.lang.instrument.Instrumentation#appendToBootstrapClassLoaderSearch
2122
// adds a jar to the bootstrap class lookup, but not to the resource lookup.
2223
// As a workaround, we keep a reference to the bootstrap jar
2324
// to use only for resource lookups.
24-
private final ClassLoader bootstrapProxy;
25-
/**
26-
* Construct a new DatadogClassLoader
27-
*
28-
* @param bootstrapJarLocation Used for resource lookups.
29-
* @param internalJarFileName File name of the internal jar
30-
* @param parent Classloader parent. Should null (bootstrap), or the platform classloader for java
31-
* 9+.
32-
*/
25+
protected final ClassLoader bootstrapProxy;
26+
private final String classLoaderName;
27+
28+
private String lastPackage = null;
29+
3330
public DatadogClassLoader(
3431
final URL bootstrapJarLocation,
3532
final String internalJarFileName,
3633
final ClassLoader bootstrapProxy,
3734
final ClassLoader parent) {
3835
super(new URL[] {}, parent);
39-
4036
this.bootstrapProxy = bootstrapProxy;
41-
37+
this.classLoaderName = null == internalJarFileName ? "datadog" : internalJarFileName;
38+
this.internalJarURLHandler =
39+
new InternalJarURLHandler(internalJarFileName, bootstrapJarLocation);
4240
try {
4341
// The fields of the URL are mostly dummy. InternalJarURLHandler is the only important
4442
// field. If extending this class from Classloader instead of URLClassloader required less
4543
// boilerplate it could be used and the need for dummy fields would be reduced
46-
47-
final URL internalJarURL =
48-
new URL(
49-
"x-internal-jar",
50-
null,
51-
0,
52-
"/",
53-
new InternalJarURLHandler(internalJarFileName, bootstrapJarLocation));
54-
55-
addURL(internalJarURL);
44+
addURL(new URL("x-internal-jar", null, 0, "/", internalJarURLHandler));
5645
} catch (final MalformedURLException e) {
5746
// This can't happen with current URL constructor
5847
log.error("URL malformed. Unsupported JDK?", e);
@@ -77,6 +66,24 @@ public boolean hasLoadedClass(final String className) {
7766
return findLoadedClass(className) != null;
7867
}
7968

69+
Class<?> loadFromPackage(String packageName, String name) throws ClassNotFoundException {
70+
if (internalJarURLHandler.getPackages().contains(packageName)) {
71+
Class<?> loaded = findLoadedClass(name);
72+
return null == loaded ? findClass(name) : loaded;
73+
}
74+
return super.loadClass(name);
75+
}
76+
77+
String getPackageName(final String className) {
78+
// intentionally not thread-safe: the worst case scenario is excess allocation/lookups
79+
String packageName = lastPackage;
80+
if (null == packageName || !className.startsWith(packageName)) {
81+
packageName = className.substring(0, className.lastIndexOf('.'));
82+
this.lastPackage = packageName;
83+
}
84+
return packageName;
85+
}
86+
8087
public ClassLoader getBootstrapProxy() {
8188
return bootstrapProxy;
8289
}
@@ -110,4 +117,45 @@ protected Class<?> findClass(final String name) throws ClassNotFoundException {
110117
throw new ClassNotFoundException(name);
111118
}
112119
}
120+
121+
@Override
122+
public String toString() {
123+
return classLoaderName;
124+
}
125+
126+
public static class DelegateClassLoader extends DatadogClassLoader {
127+
static {
128+
ClassLoader.registerAsParallelCapable();
129+
}
130+
131+
private final DatadogClassLoader shared;
132+
133+
/**
134+
* Construct a new DatadogClassLoader
135+
*
136+
* @param bootstrapJarLocation Used for resource lookups.
137+
* @param internalJarFileName File name of the internal jar
138+
* @param parent Classloader parent. Should null (bootstrap), or the platform classloader for
139+
* java 9+.
140+
*/
141+
public DelegateClassLoader(
142+
final URL bootstrapJarLocation,
143+
final String internalJarFileName,
144+
final ClassLoader bootstrapProxy,
145+
final ClassLoader parent,
146+
final ClassLoader shared) {
147+
super(bootstrapJarLocation, internalJarFileName, bootstrapProxy, parent);
148+
this.shared = (DatadogClassLoader) shared;
149+
}
150+
151+
@Override
152+
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
153+
String packageName = shared.getPackageName(name);
154+
if (internalJarURLHandler.getPackages().contains(packageName)) {
155+
Class<?> loaded = findLoadedClass(name);
156+
return null == loaded ? findClass(name) : loaded;
157+
}
158+
return shared.loadFromPackage(packageName, name);
159+
}
160+
}
113161
}

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/InternalJarURLHandler.java

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,61 @@
11
package datadog.trace.bootstrap;
22

3+
import datadog.trace.bootstrap.instrumentation.api.Pair;
34
import java.io.ByteArrayInputStream;
45
import java.io.File;
56
import java.io.IOException;
67
import java.io.InputStream;
8+
import java.lang.ref.WeakReference;
79
import java.net.URISyntaxException;
810
import java.net.URL;
911
import java.net.URLConnection;
1012
import java.net.URLStreamHandler;
11-
import java.nio.file.NoSuchFileException;
1213
import java.security.Permission;
1314
import java.util.Enumeration;
1415
import java.util.HashMap;
16+
import java.util.HashSet;
1517
import java.util.Map;
18+
import java.util.Set;
1619
import java.util.jar.JarEntry;
1720
import java.util.jar.JarFile;
1821
import lombok.extern.slf4j.Slf4j;
1922

2023
@Slf4j
2124
public class InternalJarURLHandler extends URLStreamHandler {
25+
26+
private static final WeakReference<Pair<String, JarEntry>> NULL = new WeakReference<>(null);
27+
28+
private final String name;
29+
private final FileNotInInternalJar notFound;
2230
private final Map<String, JarEntry> filenameToEntry = new HashMap<>();
23-
private JarFile bootstrapJarFile;
31+
private final Set<String> packages = new HashSet<>();
32+
private final JarFile bootstrapJarFile;
33+
34+
private WeakReference<Pair<String, JarEntry>> cache = NULL;
2435

2536
InternalJarURLHandler(final String internalJarFileName, final URL bootstrapJarLocation) {
37+
this.name = internalJarFileName;
38+
this.notFound = new FileNotInInternalJar(internalJarFileName);
2639
final String filePrefix = internalJarFileName + "/";
27-
40+
JarFile jarFile = null;
41+
String currentDir = "$";
2842
try {
2943
if (bootstrapJarLocation != null) {
30-
bootstrapJarFile = new JarFile(new File(bootstrapJarLocation.toURI()), false);
31-
final Enumeration<JarEntry> entries = bootstrapJarFile.entries();
44+
jarFile = new JarFile(new File(bootstrapJarLocation.toURI()), false);
45+
final Enumeration<JarEntry> entries = jarFile.entries();
3246
while (entries.hasMoreElements()) {
3347
final JarEntry entry = entries.nextElement();
34-
3548
if (!entry.isDirectory() && entry.getName().startsWith(filePrefix)) {
3649
String name = entry.getName();
3750
// remove data suffix
3851
int end = name.endsWith(".classdata") ? name.length() - 4 : name.length();
39-
filenameToEntry.put(name.substring(internalJarFileName.length(), end), entry);
52+
String fileName = name.substring(internalJarFileName.length(), end);
53+
filenameToEntry.put(fileName, entry);
54+
if (fileName.endsWith(".class") && !fileName.startsWith(currentDir)) {
55+
currentDir = fileName.substring(1, fileName.lastIndexOf('/'));
56+
String currentPackage = currentDir.replace('/', '.');
57+
packages.add(currentPackage);
58+
}
4059
}
4160
}
4261
}
@@ -47,6 +66,11 @@ public class InternalJarURLHandler extends URLStreamHandler {
4766
if (filenameToEntry.isEmpty()) {
4867
log.warn("No internal jar entries found");
4968
}
69+
this.bootstrapJarFile = jarFile;
70+
}
71+
72+
Set<String> getPackages() {
73+
return packages;
5074
}
5175

5276
@Override
@@ -59,12 +83,26 @@ protected URLConnection openConnection(final URL url) throws IOException {
5983
// nullInputStream() is not available until Java 11
6084
return new InternalJarURLConnection(url, new ByteArrayInputStream(new byte[0]));
6185
}
62-
final JarEntry entry = filenameToEntry.get(filename);
63-
if (null != entry) {
64-
return new InternalJarURLConnection(url, bootstrapJarFile.getInputStream(entry));
86+
// believe it or not, we're going to get called twice for this,
87+
// and the key will be a new object each time.
88+
Pair<String, JarEntry> pair = cache.get();
89+
if (null == pair || !filename.equals(pair.getLeft())) {
90+
final JarEntry entry = filenameToEntry.get(filename);
91+
if (null != entry) {
92+
pair = Pair.of(filename, entry);
93+
// this mechanism intentionally does not ensure visibility of this write, because it doesn't
94+
// matter
95+
this.cache = new WeakReference<>(pair);
96+
} else {
97+
log.debug("{} not found in {}", filename, name);
98+
throw notFound;
99+
}
65100
} else {
66-
throw new NoSuchFileException(url.getFile(), null, url.getFile() + " not in internal jar");
101+
// hack: just happen to know this only ever happens twice,
102+
// so dismiss cache after a hit
103+
this.cache = NULL;
67104
}
105+
return new InternalJarURLConnection(url, bootstrapJarFile.getInputStream(pair.getRight()));
68106
}
69107

70108
private static class InternalJarURLConnection extends URLConnection {
@@ -76,12 +114,12 @@ private InternalJarURLConnection(final URL url, final InputStream inputStream) {
76114
}
77115

78116
@Override
79-
public void connect() throws IOException {
117+
public void connect() {
80118
connected = true;
81119
}
82120

83121
@Override
84-
public InputStream getInputStream() throws IOException {
122+
public InputStream getInputStream() {
85123
return inputStream;
86124
}
87125

@@ -91,4 +129,16 @@ public Permission getPermission() {
91129
return null;
92130
}
93131
}
132+
133+
private static class FileNotInInternalJar extends IOException {
134+
135+
public FileNotInInternalJar(String jarName) {
136+
super("class not found in " + jarName);
137+
}
138+
139+
@Override
140+
public Throwable fillInStackTrace() {
141+
return this;
142+
}
143+
}
94144
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ClassLoaderMatcher.java

Lines changed: 3 additions & 0 deletions

0 commit comments

Comments
 (0)