From 40fc417db7c49b65db3a6ce247497cb6b063c427 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 12 Mar 2026 11:11:04 -0400 Subject: [PATCH] Avoid creation of empty CopyOnWriteArrayList for span links This change shares an "EMPTY" list between span when there are no span links. This avoids the construction of a CopyOnWriteArrayList, empty Object[], and internal lock Object for the common case where there are no links. This elimination of the CopyOnWriteArrayList in the empty case, reduced allocation by 5% and GC time by 7% in a span creation stress test. --- .../main/java/datadog/trace/core/DDSpan.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java index d460d5ea1bb..90ea74feebb 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java @@ -116,7 +116,8 @@ static DDSpan create( */ private volatile int longRunningVersion = 0; - protected final List links; + private static final List EMPTY = Collections.emptyList(); + protected volatile List links; /** * Spans should be constructed using the builder, not by calling the constructor directly. @@ -144,7 +145,7 @@ private DDSpan( context.getTraceCollector().touch(); // external clock: explicitly update lastReferenced } - this.links = links == null ? new CopyOnWriteArrayList<>() : new CopyOnWriteArrayList<>(links); + this.links = links == null || links.isEmpty() ? EMPTY : new CopyOnWriteArrayList<>(links); } public boolean isFinished() { @@ -879,8 +880,39 @@ public TraceConfig traceConfig() { @Override public void addLink(AgentSpanLink link) { - if (link != null) { - this.links.add(link); + if (link == null) { + return; + } + + // If links are initially null / empty, then the shared placeholder List EMPTY is used. + // Bacause EMPTY is shared, EMPTY is safe for reading, but not for writing. + // On write - if links is the EMPTY placeholder, then need to create a CopyOnWriteArrayList + // owned by this DDSpan + + // Creation of the CopyOnWriteArrayList is done via double checking locking using volatile & + // synchronized + + // If before or inside the synchronized block, links no longer points to EMPTY, + // then this thread or another thread has already handled the list construction, + // so just add to the list + + // If links still points to EMPTY inside the synchronized block, then construct a new + // CopyOnWriteArrayList + // containing the newly added link + + List links = this.links; + if (links != EMPTY) { + links.add(link); + return; + } + + synchronized (this) { + links = this.links; + if (links != EMPTY) { + links.add(link); + } else { + this.links = new CopyOnWriteArrayList<>(Collections.singletonList(link)); + } } }