Skip to content

Commit f5f83fd

Browse files
authored
Reduce memory usage for ClassLoaderHasClassesNamedMatcher (#7866)
See #7698 This is an attempt to reduce memory usage for `ClassLoaderHasClassesNamedMatcher`. Instead of having each matcher keep a `Map<ClassLoader, Boolean>` we can have one `Map<ClassLoader, BitSet>` where each matcher uses one bit in the `BitSet`. Alternatively `Map<ClassLoader, Set<ClassLoaderHasClassesNamedMatcher>>` where set contains matchers that match for given class loader would also work well because these matchers usually don't match so we can expect to have only a few elements in the set.
1 parent cf17610 commit f5f83fd

File tree

4 files changed

+75
-11
lines changed

4 files changed

+75
-11
lines changed

gradle-plugins/src/main/kotlin/io/opentelemetry/javaagent/muzzle/matcher/MuzzleGradlePluginUtil.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ class MuzzleGradlePluginUtil {
5757

5858
val matcherClass = agentClassLoader.loadClass("io.opentelemetry.javaagent.tooling.muzzle.ClassLoaderMatcher")
5959

60-
// We cannot reference Mismatch class directly here, because we are loaded from a different
61-
// class loader.
62-
6360
// We cannot reference Mismatch class directly here, because we are loaded from a different
6461
// class loader.
6562
val allMismatches = matcherClass

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/bootstrap/internal/ClassLoaderMatcherCacheHolder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@
2222
public final class ClassLoaderMatcherCacheHolder {
2323

2424
@GuardedBy("allCaches")
25-
private static final List<Cache<ClassLoader, Boolean>> allCaches = new ArrayList<>();
25+
private static final List<Cache<ClassLoader, ?>> allCaches = new ArrayList<>();
2626

2727
private ClassLoaderMatcherCacheHolder() {}
2828

29-
public static void addCache(Cache<ClassLoader, Boolean> cache) {
29+
public static void addCache(Cache<ClassLoader, ?> cache) {
3030
synchronized (allCaches) {
3131
allCaches.add(cache);
3232
}
3333
}
3434

3535
public static void invalidateAllCachesForClassLoader(ClassLoader loader) {
3636
synchronized (allCaches) {
37-
for (Cache<ClassLoader, Boolean> cache : allCaches) {
37+
for (Cache<ClassLoader, ?> cache : allCaches) {
3838
cache.remove(loader);
3939
}
4040
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/matcher/ClassLoaderHasClassesNamedMatcher.java

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@
88
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
99
import io.opentelemetry.javaagent.bootstrap.internal.ClassLoaderMatcherCacheHolder;
1010
import io.opentelemetry.javaagent.bootstrap.internal.InClassLoaderMatcher;
11+
import java.util.BitSet;
12+
import java.util.List;
13+
import java.util.concurrent.CopyOnWriteArrayList;
14+
import java.util.concurrent.atomic.AtomicInteger;
1115
import net.bytebuddy.matcher.ElementMatcher;
1216

1317
class ClassLoaderHasClassesNamedMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
14-
15-
private final Cache<ClassLoader, Boolean> cache = Cache.weak();
18+
// caching is disabled for build time muzzle checks
19+
// this field is set via reflection from ClassLoaderMatcher
20+
static boolean useCache = true;
21+
private static final AtomicInteger counter = new AtomicInteger();
1622

1723
private final String[] resources;
24+
private final int index = counter.getAndIncrement();
1825

1926
ClassLoaderHasClassesNamedMatcher(String... classNames) {
2027
resources = classNames;
2128
for (int i = 0; i < resources.length; i++) {
2229
resources[i] = resources[i].replace(".", "/") + ".class";
2330
}
24-
ClassLoaderMatcherCacheHolder.addCache(cache);
31+
if (useCache) {
32+
Manager.INSTANCE.add(this);
33+
}
2534
}
2635

2736
@Override
@@ -30,10 +39,14 @@ public boolean matches(ClassLoader cl) {
3039
// Can't match the bootstrap class loader.
3140
return false;
3241
}
33-
return cache.computeIfAbsent(cl, this::hasResources);
42+
if (useCache) {
43+
return Manager.INSTANCE.match(this, cl);
44+
} else {
45+
return hasResources(cl, resources);
46+
}
3447
}
3548

36-
private boolean hasResources(ClassLoader cl) {
49+
private static boolean hasResources(ClassLoader cl, String... resources) {
3750
boolean priorValue = InClassLoaderMatcher.getAndSet(true);
3851
try {
3952
for (String resource : resources) {
@@ -46,4 +59,41 @@ private boolean hasResources(ClassLoader cl) {
4659
}
4760
return true;
4861
}
62+
63+
private static class Manager {
64+
private static final BitSet EMPTY = new BitSet(0);
65+
static final Manager INSTANCE = new Manager();
66+
private final List<ClassLoaderHasClassesNamedMatcher> matchers = new CopyOnWriteArrayList<>();
67+
private final Cache<ClassLoader, BitSet> enabled = Cache.weak();
68+
private volatile boolean matchCalled = false;
69+
70+
Manager() {
71+
ClassLoaderMatcherCacheHolder.addCache(enabled);
72+
}
73+
74+
void add(ClassLoaderHasClassesNamedMatcher matcher) {
75+
if (matchCalled) {
76+
throw new IllegalStateException("All matchers should be create before match is called");
77+
}
78+
matchers.add(matcher);
79+
}
80+
81+
boolean match(ClassLoaderHasClassesNamedMatcher matcher, ClassLoader cl) {
82+
matchCalled = true;
83+
BitSet set = enabled.get(cl);
84+
if (set == null) {
85+
set = new BitSet(counter.get());
86+
for (ClassLoaderHasClassesNamedMatcher m : matchers) {
87+
if (hasResources(cl, m.resources)) {
88+
set.set(m.index);
89+
}
90+
}
91+
enabled.put(cl, set.isEmpty() ? EMPTY : set);
92+
enabled.put(cl, set);
93+
} else if (set.isEmpty()) {
94+
return false;
95+
}
96+
return set.get(matcher.index);
97+
}
98+
}
4999
}

muzzle/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/ClassLoaderMatcher.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
99
import io.opentelemetry.javaagent.tooling.HelperInjector;
10+
import java.lang.reflect.Field;
1011
import java.util.HashMap;
1112
import java.util.List;
1213
import java.util.Map;
@@ -32,6 +33,8 @@ public class ClassLoaderMatcher {
3233
*/
3334
public static Map<String, List<Mismatch>> matchesAll(
3435
ClassLoader classLoader, boolean injectHelpers, Set<String> excludedInstrumentationNames) {
36+
disableMatcherCache();
37+
3538
Map<String, List<Mismatch>> result = new HashMap<>();
3639
ServiceLoader.load(InstrumentationModule.class, ClassLoaderMatcher.class.getClassLoader())
3740
.forEach(
@@ -101,5 +104,19 @@ private static List<Mismatch> checkHelperInjection(
101104
return mismatches;
102105
}
103106

107+
private static void disableMatcherCache() {
108+
try {
109+
Class<?> matcherClass =
110+
Class.forName(
111+
"io.opentelemetry.javaagent.extension.matcher.ClassLoaderHasClassesNamedMatcher");
112+
Field field = matcherClass.getDeclaredField("useCache");
113+
field.setAccessible(true);
114+
field.setBoolean(null, false);
115+
} catch (Exception exception) {
116+
throw new IllegalStateException(
117+
"Failed to disable cache for ClassLoaderHasClassesNamedMatcher", exception);
118+
}
119+
}
120+
104121
private ClassLoaderMatcher() {}
105122
}

0 commit comments

Comments
 (0)