From 7b7c955d0bea2fa8756c283bd108565b7ad0a1d8 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 8 Jul 2026 11:30:16 +0200 Subject: [PATCH 1/5] ci: raise ASan test JVM heap to 1024m to fix flaky nightly OOMs Nightly asan runs repeatedly OOM with "Java heap space" at the forkEvery(25) restart boundary. -Xmx512m was sized purely to avoid colliding with ASan's shadow-memory region, but ~1.9GB of address space actually separates the pinned heap base from that region. --- .../com/datadoghq/native/config/ConfigurationPresets.kt | 6 +++++- .../kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt index 8f9f9e093e..3f91ca36c2 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/native/config/ConfigurationPresets.kt @@ -222,9 +222,13 @@ object ConfigurationPresets { // HeapBaseMinAddress is not accepted by JDK <= 11 (constraint violation); // those JDKs rely on the vm.mmap_rnd_bits=8 CI-level mitigation instead. if (PlatformUtils.testJvmMajorVersion() >= 12) { + // HeapBaseMinAddress=64MB leaves ~1.9GB of address space below the + // shadow region (0x7fff7000); 1024m keeps heap+CompressedClassSpace + // well within that margin while giving forkEvery-restarted JVMs + // enough headroom to avoid "Java heap space" OOMs seen in nightly CI. config.testJvmArgs.addAll(listOf( "-XX:HeapBaseMinAddress=0x4000000", - "-Xmx512m", + "-Xmx1024m", "-XX:CompressedClassSpaceSize=256m" )) } diff --git a/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt b/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt index f76da2c8a1..0aac0a91ae 100644 --- a/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt +++ b/build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/ProfilerTestPlugin.kt @@ -321,7 +321,7 @@ class ProfilerTestPlugin : Plugin { // https://github.com/eclipse-openj9/openj9/issues/23514 !PlatformUtils.isTestJvmJ9() } - // The ASAN test JVM is pinned to -Xmx512m (see ConfigurationPresets.kt) + // The ASAN test JVM is pinned to -Xmx1024m (see ConfigurationPresets.kt) // to keep the heap below ASan's shadow-memory region. Without forking, // Gradle reuses one JVM for the whole suite, and per-test allocations // (JFR recordings, JMC object models) accumulate until it OOMs late in From fc627d9b24b6612948cdb2f3278af753f13ceaec Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 8 Jul 2026 12:23:53 +0200 Subject: [PATCH 2/5] test: make JVMAccessTest.testGetFlag() config-aware for MaxHeapSize The asan config now overrides -Xmx to 1024m; the test previously hardcoded 512m for all configs. --- .../src/test/java/com/datadoghq/profiler/JVMAccessTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index bdd17feebb..f8ac195a67 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -77,7 +77,10 @@ void testGetFlag() { // The test relies on the gradle test task setting the JVM flags to expected values assertEquals("build/hs_err_pid%p.log", flags.getStringFlag("ErrorFile")); // set to 'build/hs_err_pid%p.log' in the test task assertTrue(flags.getBooleanFlag("ResizeTLAB")); // set to 'true' in the test task - assertEquals(512 * 1024 * 1024, flags.getIntFlag("MaxHeapSize")); // set to 512m in the test task + // asan overrides -Xmx to 1024m (see ConfigurationPresets.kt); all other configs use the 512m default. + String config = System.getProperty("ddprof_test.config"); + long expectedHeap = "asan".equals(config) ? 1024L * 1024 * 1024 : 512L * 1024 * 1024; + assertEquals(expectedHeap, flags.getIntFlag("MaxHeapSize")); assertNotNull(flags.getStringFlag("OnError")); } From c5e79d45ab4e5ac206f2a1bd9f636c99b760cfc4 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 8 Jul 2026 12:34:27 +0200 Subject: [PATCH 3/5] test: derive expected MaxHeapSize from the JVM's own -Xmx arg Replaces the per-config hardcoded heap size with a value read from RuntimeMXBean's input arguments, so the test doesn't need updating whenever a test task's -Xmx changes. --- .../com/datadoghq/profiler/JVMAccessTest.java | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index f8ac195a67..8642105500 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -3,7 +3,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.lang.management.ManagementFactory; import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -77,13 +79,32 @@ void testGetFlag() { // The test relies on the gradle test task setting the JVM flags to expected values assertEquals("build/hs_err_pid%p.log", flags.getStringFlag("ErrorFile")); // set to 'build/hs_err_pid%p.log' in the test task assertTrue(flags.getBooleanFlag("ResizeTLAB")); // set to 'true' in the test task - // asan overrides -Xmx to 1024m (see ConfigurationPresets.kt); all other configs use the 512m default. - String config = System.getProperty("ddprof_test.config"); - long expectedHeap = "asan".equals(config) ? 1024L * 1024 * 1024 : 512L * 1024 * 1024; - assertEquals(expectedHeap, flags.getIntFlag("MaxHeapSize")); + // Derive the expected heap from this JVM's own -Xmx argument rather than hardcoding a + // value, so the test stays valid regardless of what the build task passes per config. + assertEquals(configuredMaxHeapBytes(), flags.getIntFlag("MaxHeapSize")); assertNotNull(flags.getStringFlag("OnError")); } + private static long configuredMaxHeapBytes() { + List jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); + for (String arg : jvmArgs) { + if (arg.startsWith("-Xmx")) { + return parseMemorySize(arg.substring("-Xmx".length())); + } + } + throw new IllegalStateException("Test JVM was not launched with -Xmx: " + jvmArgs); + } + + private static long parseMemorySize(String value) { + char unit = Character.toLowerCase(value.charAt(value.length() - 1)); + switch (unit) { + case 'k': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L; + case 'm': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L * 1024L; + case 'g': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L * 1024L * 1024L; + default: return Long.parseLong(value); + } + } + @Test void testGetFlagMismatch() { JVMAccess.Flags flags = JVMAccess.getInstance().flags(); From 86ee933bbba3dfd87fa6535bfbf39f0288e86d06 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 8 Jul 2026 12:54:01 +0200 Subject: [PATCH 4/5] Potential fix for pull request finding 'Missing catch of NumberFormatException' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- .../com/datadoghq/profiler/JVMAccessTest.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index 8642105500..2d9c822a98 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -96,12 +96,16 @@ private static long configuredMaxHeapBytes() { } private static long parseMemorySize(String value) { - char unit = Character.toLowerCase(value.charAt(value.length() - 1)); - switch (unit) { - case 'k': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L; - case 'm': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L * 1024L; - case 'g': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L * 1024L * 1024L; - default: return Long.parseLong(value); + try { + char unit = Character.toLowerCase(value.charAt(value.length() - 1)); + switch (unit) { + case 'k': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L; + case 'm': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L * 1024L; + case 'g': return Long.parseLong(value.substring(0, value.length() - 1)) * 1024L * 1024L * 1024L; + default: return Long.parseLong(value); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid -Xmx memory size: " + value, e); } } From d9456b373a6d961713c659f4b0efd856942e5a7e Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 8 Jul 2026 13:39:41 +0200 Subject: [PATCH 5/5] fix: read the last -Xmx flag, not the first, in configuredMaxHeapBytes Addresses PR review feedback: ASan configs append a config-specific -Xmx after the standard one, and HotSpot honors the last duplicate. Co-Authored-By: Claude Sonnet 5 --- .../src/test/java/com/datadoghq/profiler/JVMAccessTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index 2d9c822a98..acb4ce3e2a 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -86,8 +86,12 @@ void testGetFlag() { } private static long configuredMaxHeapBytes() { + // HotSpot honors the last -Xmx flag on the command line when duplicates are + // present (e.g. ASan configs append a config-specific -Xmx after the + // standard one), so scan from the end to find the effective value. List jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments(); - for (String arg : jvmArgs) { + for (int i = jvmArgs.size() - 1; i >= 0; i--) { + String arg = jvmArgs.get(i); if (arg.startsWith("-Xmx")) { return parseMemorySize(arg.substring("-Xmx".length())); }