From 51c339c3a6a8819d3ab4bfd8c0e6c6cd8bf179b9 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 12 Jun 2026 10:36:35 +0200 Subject: [PATCH 1/5] collection: SDK Overhead reduction for JVM From 781e28248c2b9db0a848b9b05f7737aa73898eb4 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 12 Jun 2026 15:12:01 +0200 Subject: [PATCH 2/5] perf(core): Reduce envelope writer buffer size Use an explicit 512-character BufferedWriter buffer for envelope item and envelope serialization. This avoids allocating the oversized default char buffer for each short-lived serialization writer while preserving the existing OutputStreamWriter-based encoding path. Co-Authored-By: Claude --- .../main/java/io/sentry/JsonSerializer.java | 5 ++- .../java/io/sentry/SentryEnvelopeItem.java | 41 ++++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/sentry/src/main/java/io/sentry/JsonSerializer.java b/sentry/src/main/java/io/sentry/JsonSerializer.java index 2b24090d0cc..79a1c72bef3 100644 --- a/sentry/src/main/java/io/sentry/JsonSerializer.java +++ b/sentry/src/main/java/io/sentry/JsonSerializer.java @@ -64,6 +64,8 @@ public final class JsonSerializer implements ISerializer { @SuppressWarnings("CharsetObjectCanBeUsed") private static final Charset UTF_8 = Charset.forName("UTF-8"); + private static final int WRITER_BUFFER_SIZE = 512; + /** the SentryOptions */ private final @NotNull SentryOptions options; @@ -233,7 +235,8 @@ public void serialize(@NotNull SentryEnvelope envelope, @NotNull OutputStream ou // we do not want to close these as we would also close the stream that was passed in final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); - final Writer writer = new BufferedWriter(new OutputStreamWriter(bufferedOutputStream, UTF_8)); + final Writer writer = + new BufferedWriter(new OutputStreamWriter(bufferedOutputStream, UTF_8), WRITER_BUFFER_SIZE); try { envelope diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index dbbc36524db..728478f5906 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -41,6 +41,8 @@ public final class SentryEnvelopeItem { @SuppressWarnings("CharsetObjectCanBeUsed") private static final Charset UTF_8 = Charset.forName("UTF-8"); + private static final int WRITER_BUFFER_SIZE = 512; + private final SentryEnvelopeItemHeader header; // Either dataFactory is set or data needs to be set. private final @Nullable Callable dataFactory; @@ -85,7 +87,9 @@ public final class SentryEnvelopeItem { new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(session, writer); return stream.toByteArray(); } @@ -119,7 +123,9 @@ public final class SentryEnvelopeItem { new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(event, writer); return stream.toByteArray(); } @@ -179,7 +185,9 @@ public static SentryEnvelopeItem fromUserFeedback( new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(userFeedback, writer); return stream.toByteArray(); } @@ -206,7 +214,9 @@ public static SentryEnvelopeItem fromCheckIn( new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(checkIn, writer); return stream.toByteArray(); } @@ -344,7 +354,9 @@ private static void ensureAttachmentSizeLimit( } try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(profileChunk, writer); return stream.toByteArray(); } catch (IOException e) { @@ -403,7 +415,9 @@ private static void ensureAttachmentSizeLimit( profilingTraceData.readDeviceCpuFrequencies(); try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(profilingTraceData, writer); return stream.toByteArray(); } catch (IOException e) { @@ -437,7 +451,9 @@ private static void ensureAttachmentSizeLimit( new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(clientReport, writer); return stream.toByteArray(); } @@ -481,7 +497,8 @@ public static SentryEnvelopeItem fromReplay( try { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); final Writer writer = - new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { // relay expects the payload to be in this exact order: [event,rrweb,video] final Map replayPayload = new LinkedHashMap<>(); // first serialize replay event json bytes @@ -541,7 +558,9 @@ public static SentryEnvelopeItem fromLogs( new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(logEvents, writer); return stream.toByteArray(); } @@ -571,7 +590,9 @@ public static SentryEnvelopeItem fromMetrics( new CachedItem( () -> { try (final ByteArrayOutputStream stream = new ByteArrayOutputStream(); - final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) { + final Writer writer = + new BufferedWriter( + new OutputStreamWriter(stream, UTF_8), WRITER_BUFFER_SIZE)) { serializer.serialize(metricsEvents, writer); return stream.toByteArray(); } From 5ce5a40bb1bd95f3b9c600620b69afb2424cb027 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 18 Jun 2026 14:55:02 +0200 Subject: [PATCH 3/5] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b42d4bd09d..f52fc0a84de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - Fix performance collector scheduling many tasks in a row ([#5524](https://github.com/getsentry/sentry-java/pull/5524)) +### Internal + +- Reduce writer buffer size from 8192 to 512 ([#5544](https://github.com/getsentry/sentry-java/pull/5544)) + ## 8.43.2 ### Improvements From 247f7862d9c94501dbd9383437336e4fab3e907c Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 12 Jun 2026 10:06:31 +0200 Subject: [PATCH 4/5] perf(core): Remove redundant event map copies Avoid creating temporary maps when applying scope and options tags or scope extras. The event setters already copy these maps, so this preserves snapshot semantics while reducing allocation overhead. Co-Authored-By: Claude --- .../java/io/sentry/MainEventProcessor.java | 3 +-- .../src/main/java/io/sentry/SentryClient.java | 9 ++++---- .../java/io/sentry/MainEventProcessorTest.kt | 13 +++++++++++ .../test/java/io/sentry/SentryClientTest.kt | 18 +++++++++++++++ .../SentryBaseEventSerializationTest.kt | 23 +++++++++++++++++++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/sentry/src/main/java/io/sentry/MainEventProcessor.java b/sentry/src/main/java/io/sentry/MainEventProcessor.java index 8c684bfb65a..d84c9e47be8 100644 --- a/sentry/src/main/java/io/sentry/MainEventProcessor.java +++ b/sentry/src/main/java/io/sentry/MainEventProcessor.java @@ -11,7 +11,6 @@ import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.jetbrains.annotations.ApiStatus; @@ -191,7 +190,7 @@ private void setSdk(final @NotNull SentryBaseEvent event) { private void setTags(final @NotNull SentryBaseEvent event) { if (event.getTags() == null) { - event.setTags(new HashMap<>(options.getTags())); + event.setTags(options.getTags()); } else { for (Map.Entry item : options.getTags().entrySet()) { if (!event.getTags().containsKey(item.getKey())) { diff --git a/sentry/src/main/java/io/sentry/SentryClient.java b/sentry/src/main/java/io/sentry/SentryClient.java index 78225f05d19..a739eddd9d9 100644 --- a/sentry/src/main/java/io/sentry/SentryClient.java +++ b/sentry/src/main/java/io/sentry/SentryClient.java @@ -27,7 +27,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.jetbrains.annotations.ApiStatus; @@ -1425,7 +1424,7 @@ public void captureBatchedMetricsEvents(final @NotNull SentryMetricsEvents metri event.setUser(scope.getUser()); } if (event.getTags() == null) { - event.setTags(new HashMap<>(scope.getTags())); + event.setTags(scope.getTags()); } else { for (Map.Entry item : scope.getTags().entrySet()) { if (!event.getTags().containsKey(item.getKey())) { @@ -1483,7 +1482,7 @@ public void captureBatchedMetricsEvents(final @NotNull SentryMetricsEvents metri replayEvent.setUser(scope.getUser()); } if (replayEvent.getTags() == null) { - replayEvent.setTags(new HashMap<>(scope.getTags())); + replayEvent.setTags(scope.getTags()); } else { for (Map.Entry item : scope.getTags().entrySet()) { if (!replayEvent.getTags().containsKey(item.getKey())) { @@ -1523,7 +1522,7 @@ public void captureBatchedMetricsEvents(final @NotNull SentryMetricsEvents metri sentryBaseEvent.setUser(scope.getUser()); } if (sentryBaseEvent.getTags() == null) { - sentryBaseEvent.setTags(new HashMap<>(scope.getTags())); + sentryBaseEvent.setTags(scope.getTags()); } else { for (Map.Entry item : scope.getTags().entrySet()) { if (!sentryBaseEvent.getTags().containsKey(item.getKey())) { @@ -1537,7 +1536,7 @@ public void captureBatchedMetricsEvents(final @NotNull SentryMetricsEvents metri sortBreadcrumbsByDate(sentryBaseEvent, scope.getBreadcrumbs()); } if (sentryBaseEvent.getExtras() == null) { - sentryBaseEvent.setExtras(new HashMap<>(scope.getExtras())); + sentryBaseEvent.setExtras(scope.getExtras()); } else { for (Map.Entry item : scope.getExtras().entrySet()) { if (!sentryBaseEvent.getExtras().containsKey(item.getKey())) { diff --git a/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt b/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt index 229fd571871..fe5c835c90f 100644 --- a/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt +++ b/sentry/src/test/java/io/sentry/MainEventProcessorTest.kt @@ -358,6 +358,19 @@ class MainEventProcessorTest { } } + @Test + fun `options tags are copied when applied to event`() { + val sut = fixture.getSut(tags = mapOf("tag1" to "value1")) + val event = SentryEvent() + + sut.process(event, Hint()) + val eventTags = event.tags!! + + fixture.sentryOptions.setTag("tag2", "value2") + + assertFalse(eventTags.containsKey("tag2")) + } + @Test fun `when event has a tag set with the same name as SentryOptions tags, the tag value from the event is retained`() { val sut = fixture.getSut(tags = mapOf("tag1" to "value1", "tag2" to "value2")) diff --git a/sentry/src/test/java/io/sentry/SentryClientTest.kt b/sentry/src/test/java/io/sentry/SentryClientTest.kt index ab6fd2075a3..f51345957e4 100644 --- a/sentry/src/test/java/io/sentry/SentryClientTest.kt +++ b/sentry/src/test/java/io/sentry/SentryClientTest.kt @@ -534,6 +534,24 @@ class SentryClientTest { assertNotNull(event.request) { assertEquals("post", it.method) } } + @Test + fun `when captureEvent applies scope tags and extras, event map containers are copied`() { + val event = SentryEvent() + val scope = createScope() + + val sut = fixture.getSut() + + sut.captureEvent(event, scope) + val eventTags = event.tags!! + val eventExtras = event.extras!! + + scope.setTag("newTag", "newValue") + scope.setExtra("newExtra", "newValue") + + assertFalse(eventTags.containsKey("newTag")) + assertFalse(eventExtras.containsKey("newExtra")) + } + @Test fun `when breadcrumbs are not empty, sort them out by date`() { val b1 = Breadcrumb(DateUtils.getDateTime("2020-03-27T08:52:58.001Z")) diff --git a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt index 4cafb1ed8a8..35322d2659e 100644 --- a/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/protocol/SentryBaseEventSerializationTest.kt @@ -9,6 +9,7 @@ import io.sentry.SentryBaseEvent import io.sentry.SentryIntegrationPackageStorage import io.sentry.vendor.gson.stream.JsonToken import kotlin.test.assertEquals +import kotlin.test.assertFalse import org.junit.After import org.junit.Before import org.junit.Test @@ -102,4 +103,26 @@ class SentryBaseEventSerializationTest { assertEquals(expectedJson, actualJson) } + + @Test + fun `setTags copies source map`() { + val source = mutableMapOf("a" to "1") + val sut = Sut() + + sut.tags = source + source["b"] = "2" + + assertFalse(sut.tags!!.containsKey("b")) + } + + @Test + fun `setExtras copies source map`() { + val source = mutableMapOf("a" to "1") + val sut = Sut() + + sut.setExtras(source) + source["b"] = "2" + + assertFalse(sut.extras!!.containsKey("b")) + } } From 45c641ca1977e2cdfb9321fd3f983d00f1325982 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 18 Jun 2026 14:57:55 +0200 Subject: [PATCH 5/5] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8428f033b78..d5f18640464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,10 @@ - Fix attachments being duplicated on native events that carry scope attachments ([#5548](https://github.com/getsentry/sentry-java/pull/5548)) - Fix performance collector scheduling many tasks in a row ([#5524](https://github.com/getsentry/sentry-java/pull/5524)) +### Internal + +- Remove redundant event map copies ([#5536](https://github.com/getsentry/sentry-java/pull/5536)) + ## 8.43.2 ### Improvements