Skip to content

perf(core): [Init Reflection 1] Probe class availability without initializing#5635

Open
runningcode wants to merge 3 commits into
no/perf-init-reflectionfrom
no/perf-init-reflection-no-init
Open

perf(core): [Init Reflection 1] Probe class availability without initializing#5635
runningcode wants to merge 3 commits into
no/perf-init-reflectionfrom
no/perf-init-reflection-no-init

Conversation

@runningcode

@runningcode runningcode commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

PR Stack (Init Reflection)


Part of JAVA-587

📜 Description

LoadClass.loadClass used Class.forName(name), which initializes the class. Since this method is used purely to probe whether an optional integration is on the classpath during SentryAndroid.init, that eagerly runs unrelated static initializers — the customer trace shows androidx.compose.ui.node.Owner.<clinit> and FragmentLifecycleIntegration.<clinit> executing under the availability check.

This switches to Class.forName(name, false, classLoader) so the class is loaded but only initialized lazily on first real use (e.g. when it's actually instantiated).

💡 Motivation and Context

First of three stacked PRs reducing reflection cost on the init path, from the customer-provided Perfetto trace in the Reduce SDK init time [Android] project (JAVA-586 area).

💚 How did you test it?

New LoadClassTest including a guard asserting that probing a class does not run its static initializer; existing init/integration tests pass unchanged.

📝 Checklist

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • No breaking change or entry added to the changelog.

🔮 Next steps

PR 2 caches lookups and collapses double-probes; PR 3 gates the Compose probes behind their features.

⏱️ Pixel 3 benchmark (ART method trace → Perfetto trace_processor)

Probing a class that has a (deliberately heavy) static initializer:

old Class.forName(name) new Class.forName(name, false, loader)
target-class <clinit> invocations under the probe 1 0

The static initializer runs under the old probe and is entirely skipped under the new one. In the production trace this was androidx.compose.ui.node.Owner.<clinit> and FragmentLifecycleIntegration.<clinit> running during init. (Method tracing inflates the absolute <clinit> time, so only the invocation count is reported.)

⚠️ Merge this PR using a merge commit (not squash). Only the collection branch is squash-merged into main.

runningcode and others added 2 commits June 25, 2026 13:31
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>
@sentry

sentry Bot commented Jun 25, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.45.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 316.64 ms 393.06 ms 76.42 ms
Size 0 B 0 B 0 B

@linear-code

linear-code Bot commented Jun 25, 2026

Copy link
Copy Markdown

JAVA-587

@runningcode runningcode marked this pull request as ready for review June 25, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant