Skip to content

Commit 8fe8bad

Browse files
runningcodeclaude
andauthored
perf: Reduce reflection cost during SDK init (Init Reflection stack) (#5634)
* collection: Reduce reflection cost during SDK init * perf(core): [Init Reflection 1] Probe class availability without initializing (#5635) * perf(core): Probe class availability without initializing the class LoadClass.loadClass used Class.forName(name) which initializes the class. Used purely for availability probing during init, this eagerly runs unrelated static initializers (e.g. Compose's Owner, the fragment integration). Use Class.forName(name, false, classLoader) so the class is only initialized lazily on first real use. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * changelog * changelog: move init reflection entries to Performance * perf(core): Limit no-init class probing to isClassAvailable The previous change made loadClass itself skip class initialization, which affected callers that load a class to actually use it (NDK integration, OTEL span factory and scopes storage). Restore loadClass to its initializing behavior and confine the non-initializing probe to isClassAvailable, which is only ever used for classpath availability checks. This keeps SDK init cheap while leaving real-use callers unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 012eaeb commit 8fe8bad

3 files changed

Lines changed: 102 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
### Performance
1414

1515
- Speed up touch gesture target detection on deeply nested view hierarchies by hit-testing in local coordinates instead of calling `getLocationOnScreen` per view ([#5595](https://github.com/getsentry/sentry-java/pull/5595))
16+
- Probe class availability without initializing the class during SDK init ([#5635](https://github.com/getsentry/sentry-java/pull/5635))
1617

1718
## 8.46.0
1819

sentry/src/main/java/io/sentry/util/LoadClass.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,23 @@
1212
public class LoadClass {
1313

1414
/**
15-
* Try to load a class via reflection
15+
* Loads and initializes a class via reflection. Use this when you intend to actually use the
16+
* class (e.g. instantiate it or invoke its methods). The returned class is fully initialized, so
17+
* its static initializers run. To merely check whether a class is on the classpath, use {@link
18+
* #isClassAvailable} instead, which avoids running those initializers.
1619
*
1720
* @param clazz the full class name
1821
* @param logger an instance of ILogger
1922
* @return a Class&lt;?&gt; if it's available, or null
2023
*/
2124
public @Nullable Class<?> loadClass(final @NotNull String clazz, final @Nullable ILogger logger) {
25+
return loadClass(clazz, logger, true);
26+
}
27+
28+
private @Nullable Class<?> loadClass(
29+
final @NotNull String clazz, final @Nullable ILogger logger, final boolean initialize) {
2230
try {
23-
return Class.forName(clazz);
31+
return Class.forName(clazz, initialize, LoadClass.class.getClassLoader());
2432
} catch (ClassNotFoundException e) {
2533
if (logger != null) {
2634
logger.log(SentryLevel.INFO, "Class not available: " + clazz);
@@ -37,15 +45,35 @@ public class LoadClass {
3745
return null;
3846
}
3947

48+
/**
49+
* Probes whether a class is on the classpath without initializing it. Use this for availability
50+
* checks (e.g. deciding whether to register an integration); the class is not initialized, so its
51+
* static initializers do not run until something actually uses it. This keeps SDK init cheap by
52+
* not triggering unrelated initializers. If you need to use the class, use {@link #loadClass}
53+
* instead.
54+
*
55+
* @param clazz the full class name
56+
* @param logger an instance of ILogger
57+
* @return true if the class is on the classpath
58+
*/
4059
public boolean isClassAvailable(final @NotNull String clazz, final @Nullable ILogger logger) {
41-
return loadClass(clazz, logger) != null;
60+
return loadClass(clazz, logger, false) != null;
4261
}
4362

4463
public boolean isClassAvailable(
4564
final @NotNull String clazz, final @Nullable SentryOptions options) {
4665
return isClassAvailable(clazz, options != null ? options.getLogger() : null);
4766
}
4867

68+
/**
69+
* Like {@link #isClassAvailable}, but defers the (non-initializing) availability check until the
70+
* result is first read. Use this when the check itself should not run during SDK init but only
71+
* later, on first access.
72+
*
73+
* @param clazz the full class name
74+
* @param logger an instance of ILogger
75+
* @return a lazily-evaluated availability check
76+
*/
4977
public LazyEvaluator<Boolean> isClassAvailableLazy(
5078
final @NotNull String clazz, final @Nullable ILogger logger) {
5179
return new LazyEvaluator<>(() -> isClassAvailable(clazz, logger));
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package io.sentry.util
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertFalse
5+
import kotlin.test.assertNotNull
6+
import kotlin.test.assertNull
7+
import kotlin.test.assertTrue
8+
9+
class LoadClassTest {
10+
@Test
11+
fun `loadClass returns the class when it is available`() {
12+
assertNotNull(LoadClass().loadClass("io.sentry.SentryEvent", null))
13+
}
14+
15+
@Test
16+
fun `loadClass returns null when the class is not available`() {
17+
assertNull(LoadClass().loadClass("io.sentry.ThisClassDoesNotExist", null))
18+
}
19+
20+
@Test
21+
fun `isClassAvailable reflects whether the class is on the classpath`() {
22+
val loadClass = LoadClass()
23+
assertNotNull(loadClass.loadClass("io.sentry.SentryEvent", null))
24+
assertFalse(
25+
loadClass.isClassAvailable("io.sentry.ThisClassDoesNotExist", null as io.sentry.ILogger?)
26+
)
27+
}
28+
29+
@Test
30+
fun `isClassAvailable does not run the static initializer of the probed class`() {
31+
// Reading the flag initializes the flag holder, not the probe.
32+
assertFalse(IsClassAvailableNoInitFlag.initialized)
33+
34+
// Obtaining the name via ::class.java does not initialize the probe either.
35+
LoadClass()
36+
.isClassAvailable(IsClassAvailableNoInitProbe::class.java.name, null as io.sentry.ILogger?)
37+
38+
// Availability probing must not trigger the probe's static initializer.
39+
assertFalse(IsClassAvailableNoInitFlag.initialized)
40+
}
41+
42+
@Test
43+
fun `loadClass runs the static initializer of the loaded class`() {
44+
assertFalse(LoadClassInitFlag.initialized)
45+
46+
LoadClass().loadClass(LoadClassInitProbe::class.java.name, null)
47+
48+
assertTrue(LoadClassInitFlag.initialized)
49+
}
50+
}
51+
52+
private object IsClassAvailableNoInitFlag {
53+
@JvmField var initialized = false
54+
}
55+
56+
private object IsClassAvailableNoInitProbe {
57+
init {
58+
IsClassAvailableNoInitFlag.initialized = true
59+
}
60+
}
61+
62+
private object LoadClassInitFlag {
63+
@JvmField var initialized = false
64+
}
65+
66+
private object LoadClassInitProbe {
67+
init {
68+
LoadClassInitFlag.initialized = true
69+
}
70+
}

0 commit comments

Comments
 (0)