You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Summary: root cause is the experimental JEP 509 jdk.CPUTimeSample event on JDK 25+/Linux
TL;DR — This is a JVM-level JFR crash triggered by the new experimental CPU-time profiler that dd-trace-java enables by default on JDK 25+/Linux. It is not a disk-full / out-of-space condition, and it is independent of the native ddprof profiler. The reliable workaround is to disable the jdk.CPUTimeSample / jdk.CPUTimeSamplesLost events.
What crashed
Internal Error (jfrStreamWriterHost.inline.hpp:84)
guarantee(successful_write) failed: Not all the bytes got written, or os::write() failed
V [libjvm.so] JfrStorage::write() <- JfrRecorderService::flush() <- flushpoint() (JFR Recorder Thread)
The JVM's JFR recorder thread aborted while flushing buffered events to the on-disk chunk. The chunk writer was handed a corrupt (huge) length and os::write() failed.
Why it's the profiler / JDK 25+ specific
On JDK 25+ and Linux, dd-trace-java switches CPU profiling from the stable jdk.ExecutionSample to the experimental jdk.CPUTimeSample event (JEP 509), at throttle=9ms — see OpenJdkController.java:170-175. This is the only JDK-25-gated behavior change, and the reporter only sees crashes on JDK 25 and 26.
jdk.CPUTimeSample is a brand-new, experimental SIGPROF-driven sampler that commits JFR events from contexts JFR historically didn't have to deal with (a signal handler / a non-Java sampler thread). We've traced a path in the JVM's JFR storage where a buffer's pos < top, which underflows to a ~2 GB write length and produces exactly this guarantee crash. (Full JDK-side analysis is being filed upstream.)
Why the reporter's observations all line up
Only JDK 25 and 26 — jdk.CPUTimeSample doesn't exist before JDK 25.
dd.profiling.ddprof.enabled=false did not help — expected. The code that would suppress jdk.CPUTimeSample (OpenJdkOngoingRecording.java:58, disableOverriddenEvents) only runs when the native profiler is enabled. With ddprof.enabled=false, the experimental event is left enabled and unguarded.
Fully disabling the profiler fixes it — no JFR recording → no jdk.CPUTimeSample → no crash.
It is not disk-full — on a JDK with the JDK-8280684 fix (JDK 19+, so Corretto 25/26 both have it), an out-of-space condition aborts gracefully with "Failed to write to jfr stream because no space left on device". The reporter hit the guarantee instead, which only fires for a non-ENOSPC write failure. The host also had ~18 GB free RAM and no swap pressure.
Workaround (recommended)
Explicitly disable the experimental event (this override is applied after the JDK-25 switch, so it takes effect):
This keeps the rest of JFR-based profiling working; you lose the JDK-25 CPU-time sampling specifically. (Alternatively, re-enabling the native profiler — dd.profiling.ddprof.enabled=true, the default — also causes dd-trace-java to disable jdk.CPUTimeSample and use ddprof for CPU profiling instead.)
Suggested dd-trace-java change
jdk.CPUTimeSample is experimental in JDK 25/26 and can crash the JVM. dd-trace-java should not enable it by default on those versions. Options: gate the JDK-25+ switch behind an opt-in flag while JEP 509 is experimental; and/or disable jdk.CPUTimeSample whenever it won't be consumed (i.e. also when ddprof is disabled), so it's never left running purely as the JFR fallback. A root-cause fix for the underlying JVM bug is being pursued upstream in OpenJDK.
The last crash dump has been very useful to pin down the issue, and we now think it is a memory corruption.
The mechanism is a use-after-free: a virtual thread caches a ThreadContext whose DirectByteBuffer points into a carrier thread's ProfiledThread/OTEP record; when that carrier's OS thread exits, the native record is freed and reallocated by glibc malloc, but the dangling buffer keeps being written to.
@snuderl Would you be able to run that service with a custom-built JDK26? I'd add some debug machinery that would give us a more useful stack-trace when it crashes again.
I dont have a good answer on when exactly is started happening. We were running into virtual thread deadlocks, so we had to upgrade jdk to 26, but that also meant upgrading datadog at the same time.
For custom build, i dont think we are super comfortable with that and we also dont have a great staging environment where I could quickly reproduce.
I dont have a good answer on when exactly is started happening. We were running into virtual thread deadlocks, so we had to upgrade jdk to 26, but that also meant upgrading datadog at the same time.
Ok, that's what I suspected.
For custom build, i dont think we are super comfortable with that and we also dont have a great staging environment where I could quickly reproduce.
In that case, the best that I can do is to fix the issue opportunistically - based on what we know - in upstream JDK. And that fix would land in JDK28. Without further testing and justification (e.g. 'we tried it in our failing workload, and the problem no longer shows up'), it would be hard to argue for a backport, and in any case, such a backport would land in JDK 25, not 26. This means you'd get the fix with JDK 28 GA earliest, which is March 2027.
It also currently seems hard to come up with a mitigation on our side. So far this looks very much like a JDK bug, which is lingering since at least JDK 15, and something in your workload triggers it enough to go off the rails, the most likely suspect currently being high-rate JFR event churn, probably caused and/or amplified by virtual threads (but that is pure speculation).
Being able to run with a custom JDK26 (preferably not in prod) would be immensely helpful.
What's happening — The JVM crashes on the "JFR Recorder Thread" with guarantee(successful_write) failed in JfrStorage::write(). Not disk-full, not a Datadog bug: a latent JVM JFR data race where a buffer's pos - top transiently underflows to a multi-GB size passed to os::write. Filed upstream as JDK-8387409. CPU profiling (ddprof native) is a separate writer and isn't involved.
Fix status — JVM-side only. JDK 26 is non-LTS with a closed update train, so a real fix means a custom/patched JDK or a train that carries the backport. The steps below reduce recurrence, they don't eliminate it.
Mitigation (deploy together):
-XX:FlightRecorderOptions=threadbuffersize=1m (default 8k) — bigger buffers fill/promote far less often.
Attach the override: -Ddd.profiling.jfr-template-override-file=/path/to/jfr-churn-mitigation-override.jfp (or DD_PROFILING_JFR_TEMPLATE_OVERRIDE_FILE). You may want to adjust the timings to your needs.
Escalation if needed: hard-disable via -Ddd.profiling.disabled.events=jdk.CPUTimeSample,jdk.CPUTimeSamplesLost,jdk.SocketRead,jdk.SocketWrite,jdk.FileRead,jdk.FileWrite.
Expectations — cuts trigger rate substantially; a burst can still hit it; there's a churn floor only the JVM fix removes.
Separate item — one NoSuchMethodError: jdk.jfr.internal.JVM.getTicksFrequency()J, not from dd-agent → an app/third-party dependency with a stale JDK-internal reflection path on JDK 26; grep their deps for jdk.jfr.internal.JVM. (The MethodHandle$Holder NSMEs are benign — ignore.)
jfr-churn-mitigation-override.jfp:
# JFR template override — mitigation for the JFR chunk-writer crash (JDK-8387409)
# Raises thresholds/throttles on the highest-rate, stack-trace-bearing events to
# reduce thread-local buffer churn. MITIGATION, not a fix. Merged over dd.jfp.
# Pair with: -XX:FlightRecorderOptions=threadbuffersize=1m (default is 8k)
# Network I/O: top churn source for a network service (was 10 ms)
jdk.SocketRead#threshold=100 ms
jdk.SocketWrite#threshold=100 ms
# File I/O (was 10 ms)
jdk.FileRead#threshold=50 ms
jdk.FileWrite#threshold=50 ms
jdk.FileForce#threshold=50 ms
# Lock/monitor events (was 10 ms)
jdk.JavaMonitorEnter#threshold=50 ms
jdk.JavaMonitorInflate#threshold=50 ms
# Allocation sampling: fewer stack traces/sec (was 300/s)
jdk.ObjectAllocationSample#throttle=50/s
# Datadog product events (was 10 ms) — HIGHER TRADEOFF: these feed the
# trace<->profile timeline / endpoint hotspots; raising them lowers resolution.
datadog.Scope#threshold=50 ms
datadog.Timeline#threshold=50 ms
@snuderl do you happen to have -Ddd.profiling.start-force-first=true anywhere in your configuration? I don't see it on the cmd-line, but maybe it is in /src/integrations/datadog/dd-java-agent.properties ? If so, that might explain why the 1.64 fix did not help.
Activity
rkennke commentedon Jun 26, 2026
Summary: root cause is the experimental JEP 509 jdk.CPUTimeSample event on JDK 25+/Linux
TL;DR — This is a JVM-level JFR crash triggered by the new experimental CPU-time profiler that dd-trace-java enables by default on JDK 25+/Linux. It is not a disk-full / out-of-space condition, and it is independent of the native ddprof profiler. The reliable workaround is to disable the jdk.CPUTimeSample / jdk.CPUTimeSamplesLost events.
What crashed
Internal Error (jfrStreamWriterHost.inline.hpp:84)
guarantee(successful_write) failed: Not all the bytes got written, or os::write() failed
V [libjvm.so] JfrStorage::write() <- JfrRecorderService::flush() <- flushpoint() (JFR Recorder Thread)
The JVM's JFR recorder thread aborted while flushing buffered events to the on-disk chunk. The chunk writer was handed a corrupt (huge) length and os::write() failed.
Why it's the profiler / JDK 25+ specific
Why the reporter's observations all line up
Workaround (recommended)
Explicitly disable the experimental event (this override is applied after the JDK-25 switch, so it takes effect):
-Ddd.profiling.disabled.events=jdk.CPUTimeSample,jdk.CPUTimeSamplesLost
This keeps the rest of JFR-based profiling working; you lose the JDK-25 CPU-time sampling specifically. (Alternatively, re-enabling the native profiler — dd.profiling.ddprof.enabled=true, the default — also causes dd-trace-java to disable jdk.CPUTimeSample and use ddprof for CPU profiling instead.)
Suggested dd-trace-java change
jdk.CPUTimeSample is experimental in JDK 25/26 and can crash the JVM. dd-trace-java should not enable it by default on those versions. Options: gate the JDK-25+ switch behind an opt-in flag while JEP 509 is experimental; and/or disable jdk.CPUTimeSample whenever it won't be consumed (i.e. also when ddprof is disabled), so it's never left running purely as the JFR fallback. A root-cause fix for the underlying JVM bug is being pursued upstream in OpenJDK.
snuderl commentedon Jun 30, 2026
@rkennke we now see a different crash :) We are already fully JDK 26 so this report is valid only for that.
dump.rtf
rkennke commentedon Jun 30, 2026
Thank you! That looks like a different issue, but we don't know for sure.
Is this still dd-trace-java 1.63.0 ?
snuderl commentedon Jul 1, 2026
yes nothing else changed since the previous report, but it does only happen when profiler is active
rkennke commentedon Jul 1, 2026
The last crash dump has been very useful to pin down the issue, and we now think it is a memory corruption.
The mechanism is a use-after-free: a virtual thread caches a ThreadContext whose DirectByteBuffer points into a carrier thread's ProfiledThread/OTEP record; when that carrier's OS thread exits, the native record is freed and reallocated by glibc malloc, but the dangling buffer keeps being written to.
rkennke commentedon Jul 1, 2026
Fix is in review:
snuderl commentedon Jul 14, 2026
Hey. I did test 1.64.0 and we still get a crash
crash.txt
rkennke commentedon Jul 14, 2026
Thank you, that is interesting! Do you happen to have to core dump that the report says might be in /src/core.1 ?
rkennke commentedon Jul 14, 2026
And logs and/or stdout/stderr might also be useful, if you still have it.
snuderl commentedon Jul 14, 2026
Hmm can datadog pick those up? Unless it can we dont really get these now since pod just goes away after it crashes right now
rkennke commentedon Jul 16, 2026
what is the org-ID/service that is crashing?
rkennke commentedon Jul 16, 2026
do we know if the crash started to happen with a recent JDK version (e.g. 25 or 26)? or is this not known?
rkennke commentedon Jul 16, 2026
@snuderl Would you be able to run that service with a custom-built JDK26? I'd add some debug machinery that would give us a more useful stack-trace when it crashes again.
snuderl commentedon Jul 16, 2026
I dont have a good answer on when exactly is started happening. We were running into virtual thread deadlocks, so we had to upgrade jdk to 26, but that also meant upgrading datadog at the same time.
For custom build, i dont think we are super comfortable with that and we also dont have a great staging environment where I could quickly reproduce.
rkennke commentedon Jul 16, 2026
Ok, that's what I suspected.
In that case, the best that I can do is to fix the issue opportunistically - based on what we know - in upstream JDK. And that fix would land in JDK28. Without further testing and justification (e.g. 'we tried it in our failing workload, and the problem no longer shows up'), it would be hard to argue for a backport, and in any case, such a backport would land in JDK 25, not 26. This means you'd get the fix with JDK 28 GA earliest, which is March 2027.
It also currently seems hard to come up with a mitigation on our side. So far this looks very much like a JDK bug, which is lingering since at least JDK 15, and something in your workload triggers it enough to go off the rails, the most likely suspect currently being high-rate JFR event churn, probably caused and/or amplified by virtual threads (but that is pure speculation).
Being able to run with a custom JDK26 (preferably not in prod) would be immensely helpful.
rkennke commentedon Jul 16, 2026
What's happening — The JVM crashes on the "JFR Recorder Thread" with guarantee(successful_write) failed in JfrStorage::write(). Not disk-full, not a Datadog bug: a latent JVM JFR data race where a buffer's pos - top transiently underflows to a multi-GB size passed to os::write. Filed upstream as JDK-8387409. CPU profiling (ddprof native) is a separate writer and isn't involved.
Fix status — JVM-side only. JDK 26 is non-LTS with a closed update train, so a real fix means a custom/patched JDK or a train that carries the backport. The steps below reduce recurrence, they don't eliminate it.
Mitigation (deploy together):
-XX:FlightRecorderOptions=threadbuffersize=1m(default 8k) — bigger buffers fill/promote far less often.-Ddd.profiling.jfr-template-override-file=/path/to/jfr-churn-mitigation-override.jfp(orDD_PROFILING_JFR_TEMPLATE_OVERRIDE_FILE). You may want to adjust the timings to your needs.-Ddd.profiling.disabled.events=jdk.CPUTimeSample,jdk.CPUTimeSamplesLost,jdk.SocketRead,jdk.SocketWrite,jdk.FileRead,jdk.FileWrite.Expectations — cuts trigger rate substantially; a burst can still hit it; there's a churn floor only the JVM fix removes.
Separate item — one
NoSuchMethodError: jdk.jfr.internal.JVM.getTicksFrequency()J, not from dd-agent → an app/third-party dependency with a stale JDK-internal reflection path on JDK 26; grep their deps for jdk.jfr.internal.JVM. (The MethodHandle$Holder NSMEs are benign — ignore.)jfr-churn-mitigation-override.jfp:rkennke commentedon Jul 22, 2026
@snuderl do you happen to have -Ddd.profiling.start-force-first=true anywhere in your configuration? I don't see it on the cmd-line, but maybe it is in /src/integrations/datadog/dd-java-agent.properties ? If so, that might explain why the 1.64 fix did not help.
snuderl commentedon Jul 24, 2026
nope, nothing really related to profiler in there
Meemaw commentedon Sep 21, 2026
Hey @rkennke. Do you believe this was fixed?