Problem
Many SDK classes hold a per-instance io.sentry.util.AutoClosableReentrantLock (created eagerly in field initializers). There are ~66 new AutoClosableReentrantLock() sites across 54 files in sentry + sentry-android-core, so constructing the SDK's object graph during SentryAndroid.init allocates a lot of locks.
From the customer-provided Perfetto trace (sentryinvest), inside the SentryAndroid.init subtree:
java.util.concurrent.locks.ReentrantLock.<init> — 81 instances created
ReentrantLock$Sync.<init> — 81 (~8.7 ms self, btrace-inflated) + NonfairSync.<init> 81 (~1.3 ms)
- plus
acquire/lock/unlock/tryRelease overhead on the init path
Each ReentrantLock also allocates an AbstractQueuedSynchronizer subclass (Sync), so this is both allocation pressure (GC) and CPU on the main thread at init.
Context / constraint
These locks exist because synchronized was replaced with ReentrantLock (#3715) to avoid virtual-thread pinning (Loom). So reverting to synchronized is not the goal — we want to keep the Loom-friendly behavior while reducing the eager allocation.
Possible directions
- Lazy-allocate the lock — only create the underlying
ReentrantLock on first acquire(), so objects that never lock during init (the common case for many of these) don't allocate one. AutoClosableReentrantLock is the single chokepoint, so this could be a localized change.
- Audit hot init objects — for objects created during init that don't actually need synchronization (or are effectively single-threaded at construction), drop the lock.
- Measure GC impact via Kiln in addition to the init main-thread time.
Acceptance
Fewer ReentrantLock allocations during SentryAndroid.init (verify via on-device ART trace / Kiln), with no change to thread-safety semantics.
Problem
Many SDK classes hold a per-instance
io.sentry.util.AutoClosableReentrantLock(created eagerly in field initializers). There are ~66new AutoClosableReentrantLock()sites across 54 files insentry+sentry-android-core, so constructing the SDK's object graph duringSentryAndroid.initallocates a lot of locks.From the customer-provided Perfetto trace (
sentryinvest), inside theSentryAndroid.initsubtree:java.util.concurrent.locks.ReentrantLock.<init>— 81 instances createdReentrantLock$Sync.<init>— 81 (~8.7 ms self, btrace-inflated) +NonfairSync.<init>81 (~1.3 ms)acquire/lock/unlock/tryReleaseoverhead on the init pathEach
ReentrantLockalso allocates anAbstractQueuedSynchronizersubclass (Sync), so this is both allocation pressure (GC) and CPU on the main thread at init.Context / constraint
These locks exist because
synchronizedwas replaced withReentrantLock(#3715) to avoid virtual-thread pinning (Loom). So reverting tosynchronizedis not the goal — we want to keep the Loom-friendly behavior while reducing the eager allocation.Possible directions
ReentrantLockon firstacquire(), so objects that never lock during init (the common case for many of these) don't allocate one.AutoClosableReentrantLockis the single chokepoint, so this could be a localized change.Acceptance
Fewer
ReentrantLockallocations duringSentryAndroid.init(verify via on-device ART trace / Kiln), with no change to thread-safety semantics.