<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: jvmind</title>
    <description>The latest articles on DEV Community by jvmind (@jvmind-devel).</description>
    <link>https://dev.to/jvmind-devel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004174%2F7ba56dc1-08a2-4431-8462-b6ffd863f577.png</url>
      <title>DEV Community: jvmind</title>
      <link>https://dev.to/jvmind-devel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jvmind-devel"/>
    <language>en</language>
    <item>
      <title>The Butterfly Effect of a Compiler: Hunting an aarch64-only JVM SIGSEGV Down to the Source Line</title>
      <dc:creator>jvmind</dc:creator>
      <pubDate>Thu, 09 Jul 2026 02:35:34 +0000</pubDate>
      <link>https://dev.to/jvmind-devel/the-butterfly-effect-of-a-compiler-hunting-an-aarch64-only-jvm-sigsegv-down-to-the-source-line-4ll8</link>
      <guid>https://dev.to/jvmind-devel/the-butterfly-effect-of-a-compiler-hunting-an-aarch64-only-jvm-sigsegv-down-to-the-source-line-4ll8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A 22-kilobyte truncated ZIP file crashes an entire JVM — but only on aarch64, only with the Maven artifact, and only when built by gcc 4.9.4. A journey from a &lt;code&gt;hs_err&lt;/code&gt; log down to the exact source line, through cross-compilation, byte-level reproduction, and the ghost of a ten-year-old compiler.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Scene
&lt;/h2&gt;

&lt;p&gt;It begins, as these stories often do, with a &lt;code&gt;SIGSEGV&lt;/code&gt; that kills the entire Java process. A service running on an aarch64 machine calls into &lt;strong&gt;SevenZipJBinding&lt;/strong&gt; — the popular JNI binding around the 7-Zip/p7zip C++ library — to open a ZIP archive. For a well-formed archive, everything is fine. But feed it a &lt;strong&gt;truncated ZIP&lt;/strong&gt; — one with local file headers but no End Of Central Directory (EOCD) record — and the JVM vanishes in a puff of native code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# SIGSEGV (0xb) at pc=0x...2038, pid=..., tid=...
# Problematic frame:
# C  [lib7-Zip-JBinding.so+0x102038]
#    Java_net_sf_sevenzipjbinding_SevenZip_nativeOpenArchive+0x9b4
#
# Core dump written. Default location: /work/core or core.5856
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things make this case genuinely hard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It only happens on aarch64.&lt;/strong&gt; The x86_64 artifact is unaffected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only happens with the Maven-published artifact.&lt;/strong&gt; Rebuilding the library from source — even with the same gcc version — does &lt;em&gt;not&lt;/em&gt; reproduce it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The crash address is stable&lt;/strong&gt; (&lt;code&gt;lib7-Zip-JBinding.so+0x102038&lt;/code&gt;), but the binary is stripped, so &lt;code&gt;addr2line&lt;/code&gt; returns &lt;code&gt;??:0&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What follows is the story of how we closed all three gaps — identifying the toolchain, byte-for-byte reproducing the crash, and finally resolving the faulting instruction to a single line of C++.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Reading the Wreckage
&lt;/h2&gt;

&lt;p&gt;The HotSpot fatal error log (&lt;code&gt;hs_err_pid*.log&lt;/code&gt;) gives us the crash register state and a fragment of disassembly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0

Registers:
R0=0x0053005797794eac    R29=0x00000055039fdb30   ...

Instructions: (pc=0x...2038)
0x...2028: e2 03 15 aa a9 80 ff 97 f4 03 00 aa a0 01 66 9e
0x...2038: 16 04 40 f9 56 23 00 b4 15 08 40 f9 e0 03 15 aa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crash is a &lt;strong&gt;null-deref&lt;/strong&gt; (&lt;code&gt;si_addr=0x0&lt;/code&gt;). Let's decode the faulting instruction. The bytes at &lt;code&gt;0x...2038&lt;/code&gt; are &lt;code&gt;16 04 40 f9&lt;/code&gt;, which in little-endian is &lt;code&gt;0xf9400416&lt;/code&gt; — an A64 &lt;code&gt;LDR (immediate, unsigned offset)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;ldr&lt;/span&gt; &lt;span class="nb"&gt;x22&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;x0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;      &lt;span class="c"&gt;; load 8 bytes from address (x0 + 8)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instruction just before it (&lt;code&gt;a0 01 66 9e&lt;/code&gt; = &lt;code&gt;0x9e6601a0&lt;/code&gt;) is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;fmov&lt;/span&gt; &lt;span class="nb"&gt;x0&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;d13&lt;/span&gt;          &lt;span class="c"&gt;; move FP register d13 into GP register x0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the faulting instruction reads &lt;code&gt;[d13 + 8]&lt;/code&gt;, and &lt;code&gt;d13&lt;/code&gt; holds an invalid value. This is unusual: &lt;strong&gt;a floating-point register is being used to hold a pointer&lt;/strong&gt;. The compiler (gcc 4.9) has decided to spill a pointer into a callee-saved FP register (&lt;code&gt;d8–d15&lt;/code&gt;) to relieve register pressure on the GP file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — Disassembling the Maven Artifact
&lt;/h2&gt;

&lt;p&gt;We pull the &lt;code&gt;.so&lt;/code&gt; out of the jar and disassemble around the crash site with a cross-architecture &lt;code&gt;objdump&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;unzip &lt;span class="nt"&gt;-p&lt;/span&gt; sevenzipjbinding-linux-arm64-16.02-2.01.jar Linux-arm64/lib7-Zip-JBinding.so &lt;span class="se"&gt;\&lt;/span&gt;
  | objdump ... &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nt"&gt;--start-address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x101f00 &lt;span class="nt"&gt;--stop-address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x102200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;102014: bl   jni::JMethod::initMethodID
102018: ldr  x21, [x21, #24]
10201c: cbz  x21, 10268c
102020: mov  x0, x27               ; JNIEnv*
102024: mov  x1, x22               ; jclass
102028: mov  x2, x21               ; jmethodID
10202c: bl   JNIEnv_::NewObject    ; --- create Java object ---
102030: mov  x20, x0               ; save return value
102034: fmov x0, d13               ; x0 = d13 (this pointer)        ← ★
102038: ldr  x22, [x0, #8]         ; *** SEGV: read this-&amp;gt;field@8 *** ← CRASH
10203c: cbz  x22, 1024a4           ; NULL check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crash is right after a &lt;code&gt;NewObject&lt;/code&gt; call. The code loads a value from &lt;code&gt;d13&lt;/code&gt;, then dereferences it. This &lt;em&gt;looks&lt;/em&gt; like the textbook pattern of "forgot to check for a pending JNI exception" — and indeed, running with &lt;code&gt;-Xcheck:jni&lt;/code&gt; had already warned us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING in native method: JNI call made without checking exceptions
when required to from CallStaticObjectMethodV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that tempting narrative turns out to be &lt;strong&gt;wrong&lt;/strong&gt;. Notice the crash dereferences &lt;code&gt;d13&lt;/code&gt;, not the &lt;code&gt;NewObject&lt;/code&gt; return value (which was saved into &lt;code&gt;x20&lt;/code&gt; and never touched). So this is not a "used a NULL JNI return value" story. We need to know what &lt;code&gt;d13&lt;/code&gt; actually is.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Tracing &lt;code&gt;d13&lt;/code&gt; Back to Its Birth
&lt;/h2&gt;

&lt;p&gt;Disassembling the whole &lt;code&gt;nativeOpenArchive&lt;/code&gt; function from its start (&lt;code&gt;0x101684&lt;/code&gt;) and grepping for &lt;code&gt;d13&lt;/code&gt; reveals its entire life:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;101694: stp  d12, d13, [sp, #128]     ; prologue: save callee-saved d13
...
1017b8: bl   operator new(0x18)        ; new JNIEnvInstance (24 bytes)   ← ★
1017bc: add  x2, x29, #0xd0
1017cc: fmov d13, x2                  ; *** d13 = address of the new object ***
...
101df0: fmov x0, d13                   ; used (still valid here)
101dfc: fmov x0, d13                   ; used (still valid here)
102034: fmov x0, d13                   ; *** used again, now corrupted *** ← CRASH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;d13&lt;/code&gt; is assigned &lt;strong&gt;exactly once&lt;/strong&gt;, at &lt;code&gt;0x1017cc&lt;/code&gt;, immediately after a &lt;code&gt;new(0x18)&lt;/code&gt;. It holds the address of a freshly allocated object — specifically, a &lt;code&gt;JNIEnvInstance&lt;/code&gt;, the wrapper the library uses to scope JNI calls within this function. After the assignment, &lt;code&gt;d13&lt;/code&gt; is &lt;strong&gt;only read&lt;/strong&gt;, never written again.&lt;/p&gt;

&lt;p&gt;Here's the contradiction. &lt;code&gt;d13&lt;/code&gt; is &lt;strong&gt;callee-saved&lt;/strong&gt; (AAPCS64 reserves the low 64 bits of &lt;code&gt;d8–d15&lt;/code&gt; as callee-saved). By the ABI, every &lt;code&gt;bl&lt;/code&gt; we call between &lt;code&gt;0x1017cc&lt;/code&gt; and &lt;code&gt;0x102034&lt;/code&gt; — including &lt;code&gt;initMethodID&lt;/code&gt; and &lt;code&gt;NewObject&lt;/code&gt; — must preserve it. Yet by &lt;code&gt;0x102034&lt;/code&gt; it is garbage. &lt;strong&gt;Something along that call chain corrupted a callee-saved register that held a &lt;code&gt;this&lt;/code&gt; pointer.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — The Toolchain Fingerprint
&lt;/h2&gt;

&lt;p&gt;The artifact is stripped, so &lt;code&gt;addr2line&lt;/code&gt; gives us nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;addr2line &lt;span class="nt"&gt;-e&lt;/span&gt; lib7-Zip-JBinding.so &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 0x102038
?? ??:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to rebuild with debug info — but every rebuild we tried (gcc 11, gcc 15, even Linaro gcc 4.9.4) &lt;strong&gt;did not crash&lt;/strong&gt;. The bug refuses to be reborn from source. So our first question became: &lt;em&gt;what exactly built the Maven artifact?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The ELF &lt;code&gt;.comment&lt;/code&gt; section answers. It's written by gcc and usually survives stripping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;readelf &lt;span class="nt"&gt;-p&lt;/span&gt; .comment lib7-Zip-JBinding.so
String dump of section &lt;span class="s1"&gt;'.comment'&lt;/span&gt;:
  &lt;span class="o"&gt;[&lt;/span&gt;     0]  GCC: &lt;span class="o"&gt;(&lt;/span&gt;crosstool-NG &lt;span class="o"&gt;)&lt;/span&gt; 4.9.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;gcc 4.9.4, via crosstool-NG.&lt;/strong&gt; gcc 4.9 was the &lt;em&gt;first&lt;/em&gt; stable gcc to support aarch64, and its backend was notoriously immature — aggressive about spilling pointers into FP callee-saved registers being exactly the kind of thing it did. Later gcc rewrote the aarch64 backend; that's why every modern rebuild dodges the crash.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5 — Why "Same gcc" Still Doesn't Reproduce
&lt;/h2&gt;

&lt;p&gt;We tried rebuilding with Linaro's prebuilt gcc 4.9.4 aarch64 toolchain. Same gcc version, same &lt;code&gt;-O3&lt;/code&gt;. It did &lt;strong&gt;not&lt;/strong&gt; crash. Same compiler, different result — how?&lt;/p&gt;

&lt;p&gt;Because crosstool-NG and Linaro ship &lt;em&gt;different&lt;/em&gt; binutils, glibc, and patch sets. Code generation for UB is exquisitely sensitive to the exact binary layout, and a slightly different instruction stream shifts the register allocator's decisions. The crash lives in the intersection of "gcc 4.9.4's aarch64 backend" &lt;em&gt;and&lt;/em&gt; "the specific crosstool-NG configuration Maven used." Reproducing it required reproducing the toolchain, not just the compiler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 — Finding the Exact Toolchain
&lt;/h2&gt;

&lt;p&gt;Where did &lt;code&gt;crosstool-NG 4.9.4&lt;/code&gt; come from? The project's README pointed to &lt;strong&gt;DockCross&lt;/strong&gt; — cross-compilation toolchains shipped as Docker images. Digging into DockCross's git history, we found the smoking gun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;commit 37c54a3  Thu Apr 16 2020
  [linux-arm64] bump up the version of gcc to 8
  linux-arm64 is currently using gcc 4...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the config just before that commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CT_CC_GCC_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"4.9.4"&lt;/span&gt;
&lt;span class="nv"&gt;CT_CC_GCC_V_4_9_4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SevenZipJBinding 16.02-2.01 was released in &lt;strong&gt;January 2020&lt;/strong&gt; — three months before DockCross bumped arm64 to gcc 8. So the release was built with &lt;code&gt;dockcross/linux-arm64&lt;/code&gt; at gcc 4.9.4. The &lt;code&gt;.comment&lt;/code&gt; string is an exact match. We had the toolchain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7 — Byte-for-Byte Reproduction
&lt;/h2&gt;

&lt;p&gt;Old Docker Hub tags get garbage-collected, but the tag &lt;code&gt;dockcross/linux-arm64:20200119-1c10fb2&lt;/code&gt; was still pullable. Building SevenZipJBinding with it produced a &lt;code&gt;.so&lt;/code&gt; whose &lt;code&gt;.comment&lt;/code&gt; matched, and then — finally — the crash came back:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Maven artifact&lt;/th&gt;
&lt;th&gt;Our dockcross rebuild&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Crashes on bad.zip?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crash offset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;+0x102038&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;+0x102038&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faulting instruction bytes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9e6601a0 f9400416&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9e6601a0 f9400416&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.comment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;crosstool-NG 4.9.4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;crosstool-NG 4.9.4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Byte-identical at the crash site.&lt;/strong&gt; We now had a crashing binary whose source we controlled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8 — The Source Line
&lt;/h2&gt;

&lt;p&gt;One more rebuild, this time adding &lt;code&gt;-g3 -ggdb&lt;/code&gt; to the Release flags (keeping &lt;code&gt;-O3&lt;/code&gt; so the codegen didn't shift). The crash offset stayed at &lt;code&gt;0x102038&lt;/code&gt;, and &lt;code&gt;addr2line&lt;/code&gt; finally spoke:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;addr2line &lt;span class="nt"&gt;-e&lt;/span&gt; lib7-Zip-JBinding-dockcross-g.so &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 0x102038

JNIEnvInstance::exceptionCheck&lt;span class="o"&gt;()&lt;/span&gt;
  at jbinding-cpp/JBindingTools.h:337
  &lt;span class="o"&gt;(&lt;/span&gt;inlined by&lt;span class="o"&gt;)&lt;/span&gt; JavaToCPPSevenZip.cpp:291
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source confirms the disassembly story. In &lt;code&gt;JavaToCPPSevenZip.cpp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// line 290 - create the Java InArchiveImpl object&lt;/span&gt;
&lt;span class="n"&gt;jobject&lt;/span&gt; &lt;span class="n"&gt;inArchiveImplObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jni&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InArchiveImpl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;_newInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// -&amp;gt; NewObject&lt;/span&gt;
&lt;span class="c1"&gt;// line 291 - check whether NewObject threw&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jniEnvInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptionCheck&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                                  &lt;span class="c1"&gt;// *** CRASH ***&lt;/span&gt;
    &lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;exceptionCheck()&lt;/code&gt; is inlined from &lt;code&gt;JBindingTools.h&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;exceptionCheck&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_jniNativeCallContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;        &lt;span class="c1"&gt;// line 337 - reads this-&amp;gt;_jniNativeCallContext&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_jniNativeCallContext&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;exceptionCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_jbindingSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptionCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The member access &lt;code&gt;this-&amp;gt;_jniNativeCallContext&lt;/code&gt; compiles to &lt;code&gt;ldr x, [this, #8]&lt;/code&gt; — and &lt;code&gt;this&lt;/code&gt; is &lt;code&gt;d13&lt;/code&gt;. Everything lines up. The full causal chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A truncated ZIP (no EOCD) is opened; the archive ends up in an inconsistent state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_newInstance(env)&lt;/code&gt; calls &lt;code&gt;NewObject&lt;/code&gt; to create the Java &lt;code&gt;InArchiveImpl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;During that call, the &lt;code&gt;JNIEnvInstance::this&lt;/code&gt; pointer — held in callee-saved FP register &lt;code&gt;d13&lt;/code&gt; — gets corrupted (the physical-hardware crash gives &lt;code&gt;si_addr=0x24&lt;/code&gt;, so &lt;code&gt;d13&lt;/code&gt; became &lt;code&gt;0x1c&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The inlined &lt;code&gt;exceptionCheck()&lt;/code&gt; reads &lt;code&gt;this-&amp;gt;_jniNativeCallContext&lt;/code&gt; via &lt;code&gt;ldr x22, [d13, #8]&lt;/code&gt;, dereferences the corrupted &lt;code&gt;this&lt;/code&gt;, and faults.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;gcc 4.9.4's decision to keep &lt;code&gt;this&lt;/code&gt; in &lt;code&gt;d13&lt;/code&gt; across a JNI call is the load-bearing mistake. Every modern gcc uses a GP callee-saved register instead, which is why the bug only shows up in this one binary.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; "Build with the latest compiler" is not just about new features — for UB-laden legacy code, it can be the difference between a flawless release and a JVM that vanishes on one architecture only. A compiler bug from 2014, frozen into a published artifact, can lie dormant for years until someone hands it the wrong 22 KB of input.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;For end users, an immediate, 100% effective workaround is to validate the ZIP before handing it to SevenZipJBinding — reject anything without an EOCD record. For the project itself, the clean fix is to &lt;strong&gt;rebuild the aarch64 artifact with a modern gcc (≥5)&lt;/strong&gt;; we confirmed that gcc 8 (current DockCross), gcc 11, gcc 15, and even Linaro 4.9.4 all avoid the crash.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix — The Tools That Got Us There
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hs_err_pid*.log&lt;/code&gt; — HotSpot's register dump and the raw faulting bytes were the foundation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;readelf -p .comment&lt;/code&gt; — the toolchain fingerprint that named gcc 4.9.4.&lt;/li&gt;
&lt;li&gt;DockCross git history (&lt;code&gt;git log -S "4.9" -- linux-arm64&lt;/code&gt;) — pinned the exact image used at release time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dockcross/linux-arm64:20200119-1c10fb2&lt;/code&gt; — the time machine that reproduced the binary.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addr2line -g3&lt;/code&gt; — turned bytes into a source line.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproduce It Yourself
&lt;/h2&gt;

&lt;p&gt;All scripts used in this investigation — the dockcross reproducer, the &lt;code&gt;addr2line&lt;/code&gt; harness, and an A/B verification that proves the fix (gcc 4.9.4 crashes, gcc 12+ does not) — are open source:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔗 &lt;a href="https://github.com/jvmind/jvm-deep-dives" rel="noopener noreferrer"&gt;github.com/jvmind/jvm-deep-dives&lt;/a&gt;&lt;/strong&gt; → &lt;code&gt;case-01-sevenzipjbinding-aarch64-sigsegv/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The scripts are Docker-based and run end-to-end on a regular x86_64 machine (they use QEMU user-mode to run the arm64 JVM; see the repo's README for the cross-architecture setup guide). Clone it and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="nt"&gt;-01-sevenzipjbinding-aarch64-sigsegv&lt;/span&gt;
./verify_fix.sh    &lt;span class="c"&gt;# builds both toolchains, runs the crash test on each&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Written after a multi-day debugging session. All disassembly, register values, and reproduction steps are verbatim from the actual investigation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>aarch64</category>
      <category>native</category>
      <category>java</category>
    </item>
    <item>
      <title>Can AI Diagnose JVM Incidents? Correlating GC Logs, Thread Dumps, and Heap Dumps</title>
      <dc:creator>jvmind</dc:creator>
      <pubDate>Mon, 06 Jul 2026 01:34:28 +0000</pubDate>
      <link>https://dev.to/jvmind-devel/inside-jvmind-how-the-ai-agent-correlates-gc-logs-thread-dumps-and-heap-dumps-using-a-react-loop-5aoa</link>
      <guid>https://dev.to/jvmind-devel/inside-jvmind-how-the-ai-agent-correlates-gc-logs-thread-dumps-and-heap-dumps-using-a-react-loop-5aoa</guid>
      <description>&lt;p&gt;Traditional JVM analysis tools parse logs.&lt;/p&gt;

&lt;p&gt;JVMind runs a reasoning loop.&lt;/p&gt;

&lt;p&gt;Instead of treating GC logs, thread dumps, and heap dumps as independent artifacts, JVMind’s AI Agent orchestrates a multi-step investigation process using a ReAct-style tool-calling architecture.&lt;/p&gt;

&lt;p&gt;This article explains how that works under the hood.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem: JVM Diagnostics Are Multi-Dimensional
&lt;/h2&gt;

&lt;p&gt;A typical JVM incident involves multiple dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Memory allocation behavior (GC log)&lt;/li&gt;
&lt;li&gt;Object retention structure (Heap dump)&lt;/li&gt;
&lt;li&gt;Thread state and execution pattern (jstack)&lt;/li&gt;
&lt;li&gt;Application execution logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most tools parse one of these dimensions.&lt;/p&gt;

&lt;p&gt;But real root causes live in the intersections.&lt;/p&gt;

&lt;p&gt;To detect those patterns automatically, JVMind uses a structured reasoning pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  High-Level Architecture
&lt;/h1&gt;

&lt;p&gt;JVMind is composed of four layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Artifact Parsers (deterministic analyzers)&lt;/li&gt;
&lt;li&gt;Structured Signal Extractors&lt;/li&gt;
&lt;li&gt;ReAct-based AI Agent&lt;/li&gt;
&lt;li&gt;Evidence Correlation Engine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrltbn3r35i70wpgguz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrltbn3r35i70wpgguz5.png" alt=" " width="799" height="263"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1 — Deterministic Parsing Layer
&lt;/h1&gt;

&lt;p&gt;Before any AI reasoning begins, JVMind parses each artifact into structured data.&lt;/p&gt;




&lt;h2&gt;
  
  
  GC Log Analysis Interface
&lt;/h2&gt;

&lt;p&gt;The GC report interface provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allocation rate&lt;/li&gt;
&lt;li&gt;GC frequency&lt;/li&gt;
&lt;li&gt;Full GC ratio&lt;/li&gt;
&lt;li&gt;Pause distribution&lt;/li&gt;
&lt;li&gt;Throughput&lt;/li&gt;
&lt;li&gt;Heap occupancy trend&lt;/li&gt;
&lt;li&gt;Reclamation efficiency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmco240gxnztz8kps614s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmco240gxnztz8kps614s.png" alt="BQACAgUAAyEGAASHRsPbAAEWqKBqSmM_LQ4DvP9Qx67QTtJVJ_I5eAAClCcAApTnUVa5zVjYCaGUiTwE.png" width="800" height="1252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output is converted into structured signals like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;high_full_gc_ratio&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;low_reclaim_efficiency&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allocation_rate_exceeds_heap_capacity&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Thread Dump (jstack) Analysis Interface
&lt;/h2&gt;

&lt;p&gt;The jstack analyzer extracts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thread state distribution&lt;/li&gt;
&lt;li&gt;Deadlocks&lt;/li&gt;
&lt;li&gt;Lock chains&lt;/li&gt;
&lt;li&gt;BLOCKED hotspots&lt;/li&gt;
&lt;li&gt;RUNNABLE-heavy CPU scenarios&lt;/li&gt;
&lt;li&gt;Executor patterns&lt;/li&gt;
&lt;li&gt;Stack frame summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxusnyma40e83o2xa1gwb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxusnyma40e83o2xa1gwb.png" alt="BQACAgUAAyEGAASHRsPbAAEWqKZqSmPAqS3tITek03FYNlBxlNIeAAObJwAClOdRVlribhRKqH1APAQ.png" width="800" height="1340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This produces signals such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;executor_waiting_pattern&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;high_runnable_ratio&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;no_deadlock_detected&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Heap Dump Analysis Interface
&lt;/h2&gt;

&lt;p&gt;The heap analyzer extracts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object count by class&lt;/li&gt;
&lt;li&gt;Retained size&lt;/li&gt;
&lt;li&gt;Dominator tree&lt;/li&gt;
&lt;li&gt;Top memory consumers&lt;/li&gt;
&lt;li&gt;Leak suspects&lt;/li&gt;
&lt;li&gt;Ownership chains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqnq7ff402kn7wlwuvine.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqnq7ff402kn7wlwuvine.png" alt="BQACAgUAAyEGAASHRsPbAAEWqKVqSmO-CT-GJS2HNl0MFhuyUCOH-QACmicAApTnUVaZQGbD9Fx2nzwE.png" width="800" height="1347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this, structured signals are generated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;heap_domination_by_single_class&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;large_number_of_virtual_threads&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dominant_executor_related_objects&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 2 — Signal Structuring
&lt;/h1&gt;

&lt;p&gt;Instead of letting the AI read raw logs, JVMind feeds it structured signals.&lt;/p&gt;

&lt;p&gt;Each signal includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metric value&lt;/li&gt;
&lt;li&gt;Severity&lt;/li&gt;
&lt;li&gt;Context&lt;/li&gt;
&lt;li&gt;Supporting evidence&lt;/li&gt;
&lt;li&gt;Confidence level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents hallucination.&lt;/p&gt;

&lt;p&gt;The AI agent reasons only on verified extracted facts.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 3 — ReAct-Based AI Agent
&lt;/h1&gt;

&lt;p&gt;JVMind uses a ReAct-style reasoning loop.&lt;/p&gt;

&lt;p&gt;ReAct = Reason + Act.&lt;/p&gt;

&lt;p&gt;Instead of generating one large answer, the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Observes signals&lt;/li&gt;
&lt;li&gt;Forms a hypothesis&lt;/li&gt;
&lt;li&gt;Calls analyzer tools&lt;/li&gt;
&lt;li&gt;Gathers more evidence&lt;/li&gt;
&lt;li&gt;Refines reasoning&lt;/li&gt;
&lt;li&gt;Produces structured diagnosis&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Agent Execution Interface
&lt;/h2&gt;

&lt;p&gt;The Agent UI shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observations&lt;/li&gt;
&lt;li&gt;Tool calls&lt;/li&gt;
&lt;li&gt;Intermediate reasoning&lt;/li&gt;
&lt;li&gt;Evidence collection&lt;/li&gt;
&lt;li&gt;Final structured diagnosis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbcb4tz3cxfrpsp8t8juc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbcb4tz3cxfrpsp8t8juc.png" alt="BQACAgUAAyEGAASHRsPbAAEWqI1qSmGwg9ItxqOWWn0AARw6xDb-wFsAAn4nAAKU51FW4ZTqMVoU_VQ8BA.png" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This transparency is critical.&lt;/p&gt;

&lt;p&gt;The diagnosis is not a black box.&lt;/p&gt;

&lt;p&gt;It shows how conclusions are reached.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example: Virtual Thread Explosion
&lt;/h1&gt;

&lt;p&gt;Let’s walk through the correlation process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1 — GC Observation
&lt;/h2&gt;

&lt;p&gt;Signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;266 GCs in 23 seconds&lt;/li&gt;
&lt;li&gt;107 Full GCs&lt;/li&gt;
&lt;li&gt;Throughput 15.7%&lt;/li&gt;
&lt;li&gt;Full GC reclaims zero bytes&lt;/li&gt;
&lt;li&gt;Allocation rate 157 MB/s&lt;/li&gt;
&lt;li&gt;Heap size 128 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent hypothesis:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Severe allocation pressure with low reclamation efficiency.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Phase 2 — Heap Correlation
&lt;/h2&gt;

&lt;p&gt;Signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;191,782 VirtualThread instances&lt;/li&gt;
&lt;li&gt;74% heap retained&lt;/li&gt;
&lt;li&gt;Dominator tree confirms VT ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent refines hypothesis:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Massive virtual thread accumulation dominating heap.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Phase 3 — Thread Behavior Analysis
&lt;/h2&gt;

&lt;p&gt;Signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No deadlocks&lt;/li&gt;
&lt;li&gt;Executor waiting in awaitTermination()&lt;/li&gt;
&lt;li&gt;51/56 threads RUNNABLE&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent refines hypothesis:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Submission rate likely exceeds completion rate.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Phase 4 — Cross-Dimension Validation
&lt;/h2&gt;

&lt;p&gt;The agent cross-validates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allocation rate vs heap size&lt;/li&gt;
&lt;li&gt;Virtual thread count vs retained size&lt;/li&gt;
&lt;li&gt;Executor state vs object retention&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final conclusion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unbounded virtual thread creation under undersized heap caused GC death spiral and OOM.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Structured Root Cause Output
&lt;/h1&gt;

&lt;p&gt;Instead of free text, JVMind outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executive summary&lt;/li&gt;
&lt;li&gt;Cross-artifact evidence&lt;/li&gt;
&lt;li&gt;Root cause chain&lt;/li&gt;
&lt;li&gt;Confidence level&lt;/li&gt;
&lt;li&gt;Supporting metrics&lt;/li&gt;
&lt;li&gt;Remediation recommendations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes the output production-ready.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why ReAct Is Critical
&lt;/h1&gt;

&lt;p&gt;Without tool-calling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model might blame GC&lt;/li&gt;
&lt;li&gt;Or misinterpret RUNNABLE threads&lt;/li&gt;
&lt;li&gt;Or miss heap dominance patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ReAct loop enforces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Evidence collection&lt;/li&gt;
&lt;li&gt;Hypothesis validation&lt;/li&gt;
&lt;li&gt;Cross-checking signals&lt;/li&gt;
&lt;li&gt;Iterative refinement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It mimics how senior JVM engineers reason.&lt;/p&gt;




&lt;h1&gt;
  
  
  Hallucination Control
&lt;/h1&gt;

&lt;p&gt;JVMind prevents hallucination by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic parsing&lt;/li&gt;
&lt;li&gt;Structured signals&lt;/li&gt;
&lt;li&gt;Evidence linking&lt;/li&gt;
&lt;li&gt;No free-form log interpretation&lt;/li&gt;
&lt;li&gt;Confidence scoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every conclusion is tied to specific metrics and objects.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Approach Matters
&lt;/h1&gt;

&lt;p&gt;Manual JVM troubleshooting requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Context switching between tools&lt;/li&gt;
&lt;li&gt;Deep JVM internals knowledge&lt;/li&gt;
&lt;li&gt;Hours of cross-referencing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JVMind compresses that process into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parsing&lt;/li&gt;
&lt;li&gt;Signal extraction&lt;/li&gt;
&lt;li&gt;ReAct reasoning&lt;/li&gt;
&lt;li&gt;Evidence-backed diagnosis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It does not replace engineers.&lt;/p&gt;

&lt;p&gt;It accelerates them.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thought
&lt;/h1&gt;

&lt;p&gt;JVM incidents are interaction failures.&lt;/p&gt;

&lt;p&gt;GC behavior.&lt;br&gt;&lt;br&gt;
Thread lifecycle.&lt;br&gt;&lt;br&gt;
Heap structure.&lt;br&gt;&lt;br&gt;
Application logic.  &lt;/p&gt;

&lt;p&gt;JVMind’s ReAct-based AI Agent reasons across these dimensions instead of summarizing them individually.&lt;/p&gt;

&lt;p&gt;That is the difference between reading logs and diagnosing systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  Try JVMind
&lt;/h1&gt;

&lt;p&gt;Upload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GC logs
&lt;/li&gt;
&lt;li&gt;Thread dumps
&lt;/li&gt;
&lt;li&gt;Heap dumps
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And let JVMind correlate them automatically.&lt;/p&gt;

&lt;p&gt;Demo available.&lt;br&gt;&lt;br&gt;
No signup required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jvmind.io" rel="noopener noreferrer"&gt;https://jvmind.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>java</category>
      <category>jvm</category>
    </item>
    <item>
      <title>Virtual Thread OOM – A Case Study in Missing Backpressure</title>
      <dc:creator>jvmind</dc:creator>
      <pubDate>Fri, 03 Jul 2026 07:02:54 +0000</pubDate>
      <link>https://dev.to/jvmind-devel/virtual-thread-oom-a-case-study-in-missing-backpressure-5ebk</link>
      <guid>https://dev.to/jvmind-devel/virtual-thread-oom-a-case-study-in-missing-backpressure-5ebk</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: A missing &lt;code&gt;Semaphore&lt;/code&gt; in a virtual thread stress test led to 1,076 MB/s allocation rate, 324 Full GCs releasing &lt;strong&gt;0 bytes&lt;/strong&gt;, and 150k new virtual threads in 1 second. Virtual threads are powerful, but &lt;code&gt;submit()&lt;/code&gt; is non-blocking – you must manage the creation rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Incident
&lt;/h3&gt;

&lt;p&gt;A stress test using JDK 26 virtual threads (Loom) ran for ~134 seconds before OOM. The application was using &lt;code&gt;Executors.newVirtualThreadPerTaskExecutor()&lt;/code&gt; to submit short-lived tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  GC Report – What the Logs Showed
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Heap Size&lt;/td&gt;
&lt;td&gt;2 GB (G1)&lt;/td&gt;
&lt;td&gt;–&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allocation Rate&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,076 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2GB heap filled in &amp;lt;2 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total GC Events&lt;/td&gt;
&lt;td&gt;607&lt;/td&gt;
&lt;td&gt;~4.5 GCs per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full GC Count&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;324 (53%)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;More Full GCs than Young GCs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full GC Total Pause&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;118,114 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;88% of runtime spent in Full GC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Throughput&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~10%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JVM barely ran the app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top Full GC Releases&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2047MB → 2047MB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 bytes released&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "0 bytes released" pattern across 324 Full GCs is the critical signal: everything in the heap was alive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread Dump Analysis
&lt;/h3&gt;

&lt;p&gt;Two jstack dumps taken 1 second apart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;jstack-1: Virtual thread #3342482&lt;/li&gt;
&lt;li&gt;jstack-2: Virtual thread #3492195&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+150,000 virtual thread IDs in 1 second&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Carrier thread stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ForkJoinPool-1-worker-1 (daemon)
  → Carrying virtual thread #3342482 / #3492195
     at ConcurrentHashMap.sumCount / isEmpty
     at ThreadPerTaskExecutor.tryTerminate / taskComplete
     at VirtualThread.run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thread state distribution: 64 total platform threads, 49 carrying virtual threads, 0 BLOCKED, 0 deadlocks – consistent with a virtual thread explosion.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Offending Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;submitTasks&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;threadCount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;workMs&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExecutorService&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// ← no sleep, no rate limiting&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threadCount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// ← 10,000 per round&lt;/span&gt;
                &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// ← non-blocking, returns instantly&lt;/span&gt;
                    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workMs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ← each thread lives ≥10ms&lt;/span&gt;
                    &lt;span class="c1"&gt;// do work&lt;/span&gt;
                &lt;span class="o"&gt;});&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The cascade&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;~100-200 loop iterations/sec × 10,000 threads = &lt;strong&gt;~1-2 million &lt;code&gt;submit()&lt;/code&gt; calls/sec&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Each virtual thread carries Continuation stack + ThreadLocal + task object&lt;/li&gt;
&lt;li&gt;Each thread lives at least 10ms → unbounded accumulation&lt;/li&gt;
&lt;li&gt;2GB heap fills in &amp;lt;2 seconds&lt;/li&gt;
&lt;li&gt;Young GCs (205) release ~8MB avg → can't keep up&lt;/li&gt;
&lt;li&gt;G1 falls back to Full GC → 324 Full GCs, all release 0 bytes&lt;/li&gt;
&lt;li&gt;JVM spends 88% of time in GC → OOM&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why Full GC Released 0 Bytes
&lt;/h3&gt;

&lt;p&gt;Full GC traces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[Full GC (G1 Evacuation Pause) 2047M-&amp;gt;&lt;/span&gt;2047M, 0.362s]
&lt;span class="gp"&gt;[Full GC (G1 Evacuation Pause) 2047M-&amp;gt;&lt;/span&gt;2047M, 0.371s]
&lt;span class="gp"&gt;[Full GC (G1 Evacuation Pause) 2047M-&amp;gt;&lt;/span&gt;2047M, 0.358s]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All virtual threads and Continuations were alive – referenced by &lt;code&gt;ForkJoinPool&lt;/code&gt; and the &lt;code&gt;ThreadPerTaskExecutor&lt;/code&gt;. With all objects reachable from GC roots, the collector had nothing to reclaim.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix – Adding Backpressure
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Option 1: Semaphore (Recommended)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxPending&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threadCount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;acquire&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// blocks when limit exceeded&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* do work */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;});&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Option 2: Add Sleep in the Loop
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;threadCount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(...);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workMs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// rate limit submissions&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Option 3: Bounded Queue + CallerRunsPolicy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BlockingQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Runnable&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayBlockingQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MAX_VALUE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeUnit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SECONDS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofVirtual&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CallerRunsPolicy&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// throttles the submitter&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Lessons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Virtual threads are not fire-and-forget&lt;/strong&gt; – &lt;code&gt;submit()&lt;/code&gt; is non-blocking by design, which means &lt;strong&gt;you&lt;/strong&gt; must manage the creation rate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full GC releasing 0 bytes is a strong diagnostic signal&lt;/strong&gt; – it almost always means thread explosion or a massive live object graph&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The virtual thread IDs in jstack don't lie&lt;/strong&gt; – 150k new IDs in 1 second is a clear warning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpressure is not optional&lt;/strong&gt; – without it, any fast producer can overwhelm the JVM&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Data Mapping – GC + Threads + Code
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GC/Thread Symptom&lt;/th&gt;
&lt;th&gt;Root Cause in Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Allocation rate 1,076 MB/s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;while&lt;/code&gt; no sleep + non-blocking &lt;code&gt;submit()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;324 Full GCs, 0 bytes released&lt;/td&gt;
&lt;td&gt;All virtual threads alive, GC roots reachable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;150k new threads in 1 second&lt;/td&gt;
&lt;td&gt;~1M &lt;code&gt;submit()&lt;/code&gt; calls per second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput ~10%&lt;/td&gt;
&lt;td&gt;88% of time in Full GC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Tool Note
&lt;/h3&gt;

&lt;p&gt;Both analyses were performed using a JVM analysis tool I'm building – it parses GC logs, correlates with thread dumps, and extracts root cause patterns. The tool helped identify these issues in minutes rather than hours.&lt;/p&gt;

</description>
      <category>java</category>
      <category>virtualthread</category>
      <category>jdk21</category>
    </item>
    <item>
      <title>JDK 26 G1 GC Dual Card Tables – A Benchmark Story</title>
      <dc:creator>jvmind</dc:creator>
      <pubDate>Mon, 29 Jun 2026 09:42:23 +0000</pubDate>
      <link>https://dev.to/jvmind-devel/jdk-26-g1-gc-dual-card-tables-a-benchmark-story-24a9</link>
      <guid>https://dev.to/jvmind-devel/jdk-26-g1-gc-dual-card-tables-a-benchmark-story-24a9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: JDK 26's G1 write barrier optimization (Dual Card Tables) delivers ~2.4x faster write barrier operations, but aggregate GC metrics can be misleading if you don't account for the application doing more work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;The Dual Card Tables work landed in JDK 26, promising 5-15% throughput improvements for G1 GC. I wanted to understand how this behaves under a write-barrier-heavy workload, so I ran a controlled benchmark comparing JDK 25 vs JDK 26 G1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workload&lt;/strong&gt;: Write-barrier-heavy allocation test (storing newly allocated Objects into a fixed array)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heap&lt;/strong&gt;: 2GB, G1 GC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt;: ~31 seconds per test&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JDKs&lt;/strong&gt;: 25 vs 26 (both with G1)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initial Observations (Misleading)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;JDK 25&lt;/th&gt;
&lt;th&gt;JDK 26&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GC Events&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;168&lt;/td&gt;
&lt;td&gt;+124%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Pause Time&lt;/td&gt;
&lt;td&gt;1.78s&lt;/td&gt;
&lt;td&gt;3.26s&lt;/td&gt;
&lt;td&gt;+83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;94.30%&lt;/td&gt;
&lt;td&gt;89.50%&lt;/td&gt;
&lt;td&gt;-4.8 p.p.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allocation Rate&lt;/td&gt;
&lt;td&gt;2,874 MB/s&lt;/td&gt;
&lt;td&gt;6,587 MB/s&lt;/td&gt;
&lt;td&gt;+129%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the surface, JDK 26 looked worse: more GC events, more total pause time, lower throughput. But this was a measurement artifact.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Critical Data Point
&lt;/h3&gt;

&lt;p&gt;The benchmark's raw output told a different story:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JDK&lt;/th&gt;
&lt;th&gt;Result (ms/op)&lt;/th&gt;
&lt;th&gt;Iterations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;0.055 ± 0.013&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.023 ± 0.003&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;129&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;JDK 26 executes the same write-barrier operation in less than half the time&lt;/strong&gt; – ~2.4x faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Actually Happened
&lt;/h3&gt;

&lt;p&gt;The allocation rate spike (2,874 → 6,587 MB/s) wasn't a regression. It was a consequence of the application running faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Allocation Rate = Allocated Bytes / Application Runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the write barrier becomes faster, the application spends less time on barrier operations and more time actually doing work – so it allocates more bytes in the same wall-clock time. More allocations → more garbage → more GC events → more total pause time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "throughput regression" was actually a sign of throughput improvement.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Corrected Conclusion
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;JDK 26 vs JDK 25&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write barrier performance&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;~2.4x faster&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single-pause latency&lt;/td&gt;
&lt;td&gt;✅ Better across all percentiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective throughput&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Significantly higher&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC events (count)&lt;/td&gt;
&lt;td&gt;⚠️ Higher (because of more work)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total pause time&lt;/td&gt;
&lt;td&gt;⚠️ Higher (because of more work)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Key Takeaway
&lt;/h3&gt;

&lt;p&gt;Aggregate GC metrics like "total pause time" or "throughput percentage" are not absolute measures of performance. They must be interpreted in context. JDK 26's G1 optimization is a clear win – it made the application run faster, which created more garbage, which triggered more GC activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version – full code available on request&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WriteBarrierBench&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="no"&gt;ARRAY_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;ARRAY_SIZE&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;blackhole&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;storeReferences&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// triggers write barrier&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;blackhole&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// prevents optimization&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... measurement harness with warmup, iterations, etc.&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Methodology Note
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The benchmark uses a &lt;code&gt;volatile long blackhole&lt;/code&gt; to prevent dead code elimination&lt;/li&gt;
&lt;li&gt;Warmup iterations are included to allow JIT compilation&lt;/li&gt;
&lt;li&gt;A bash harness controls JDK switching and GC logging&lt;/li&gt;
&lt;li&gt;The test is controlled (single workload pattern) – results may not generalize to all allocation profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Open Questions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How does this scale with different heap sizes?&lt;/li&gt;
&lt;li&gt;What does the behavior look like on other GC algorithms (Parallel, ZGC)?&lt;/li&gt;
&lt;li&gt;Is there a direct way to measure write barrier overhead independently?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>performance</category>
      <category>programming</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>jvmind</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:09:31 +0000</pubDate>
      <link>https://dev.to/jvmind-devel/-3mad</link>
      <guid>https://dev.to/jvmind-devel/-3mad</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/jvmind-devel/debugging-a-c2-jit-compiler-infinite-loop-on-aarch64-2mbc" class="crayons-story__hidden-navigation-link"&gt;Debugging a C2 JIT Compiler Infinite Loop on AArch64&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/jvmind-devel" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004174%2F7ba56dc1-08a2-4431-8462-b6ffd863f577.png" alt="jvmind-devel profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jvmind-devel" class="crayons-story__secondary fw-medium m:hidden"&gt;
              jvmind
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                jvmind
                
              
              &lt;div id="story-author-preview-content-3999085" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jvmind-devel" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004174%2F7ba56dc1-08a2-4431-8462-b6ffd863f577.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;jvmind&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/jvmind-devel/debugging-a-c2-jit-compiler-infinite-loop-on-aarch64-2mbc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 26&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/jvmind-devel/debugging-a-c2-jit-compiler-infinite-loop-on-aarch64-2mbc" id="article-link-3999085"&gt;
          Debugging a C2 JIT Compiler Infinite Loop on AArch64
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/java"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;java&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jvm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jvm&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/performance"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;performance&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aarch64"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aarch64&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/jvmind-devel/debugging-a-c2-jit-compiler-infinite-loop-on-aarch64-2mbc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Debugging a C2 JIT Compiler Infinite Loop on AArch64</title>
      <dc:creator>jvmind</dc:creator>
      <pubDate>Fri, 26 Jun 2026 14:45:47 +0000</pubDate>
      <link>https://dev.to/jvmind-devel/debugging-a-c2-jit-compiler-infinite-loop-on-aarch64-2mbc</link>
      <guid>https://dev.to/jvmind-devel/debugging-a-c2-jit-compiler-infinite-loop-on-aarch64-2mbc</guid>
      <description>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: A production Java 8 service on AArch64 experienced 100% CPU on a single core caused by a C2 JIT compiler infinite loop. The root cause was a cycle in C2's Ideal Graph triggered by dead code elimination of MemBarCPUOrder nodes. A verified workaround: &lt;code&gt;-XX:CompileCommand=exclude,org.springframework.core.MethodParameter::getParameterType&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prologue
&lt;/h2&gt;

&lt;p&gt;A mysterious CPU spike appeared on a production Java 8 service running on OpenJDK 8u442 on AArch64 processors.&lt;/p&gt;

&lt;p&gt;The symptom: one core was pinned at 100%, the entire application became sluggish, and top showed C2 CompilerThread0 as the culprit.&lt;/p&gt;

&lt;p&gt;This issue was not immediately reproducible on demand. It only surfaced after sustained mixed workload, making it particularly challenging to diagnose.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Flame Graph Revelation
&lt;/h2&gt;

&lt;p&gt;We started with a flame graph taken during the incident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80% of samples landed inside MemNode::can_see_stored_value&lt;/li&gt;
&lt;li&gt;21% were in MergeMemNode::memory_at&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two functions, deep in the OpenJDK C2 compiler, were burning cycles. This was definitely an infinite loop inside the C2 compiler.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Suspect Loop
&lt;/h2&gt;

&lt;p&gt;A careful reading of memnode.cpp (from OpenJDK 8u442 source) revealed a potential while loop that could spin without exit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;is_Proj&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;opc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Opcode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Op_MemBarRelease&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;opc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Op_StoreFence&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; 
        &lt;span class="n"&gt;opc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Op_MemBarAcquire&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;opc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Op_MemBarCPUOrder&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TypeFunc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;is_MergeMem&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MergeMemNode&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;as_MergeMem&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;new_st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;memory_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alias_idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_st&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;base_memory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_st&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ← INFINITE LOOP RISK&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_st&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The continue inside the while combined with current = new_st is the critical pattern. If new_st equals current, the loop never terminates.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why Only AArch64?
&lt;/h2&gt;

&lt;p&gt;We tried to reproduce on x86 – nothing. On AArch64 – the hang appeared after sustained operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Model&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;x86 (TSO) is strongly ordered. C2 rarely inserts MemBarCPUOrder barriers.&lt;/li&gt;
&lt;li&gt;AArch64 (weak memory model) requires explicit barriers for safe publication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bug only fires when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is a loop that creates objects and writes to a volatile field.&lt;/li&gt;
&lt;li&gt;C2 performs dead code elimination on a path inside that loop.&lt;/li&gt;
&lt;li&gt;The cleanup folds a MergeMem node and makes its base_memory point back to the loop's own Proj node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates a cycle in the data-flow graph:&lt;/p&gt;

&lt;p&gt;Proj → MemBarCPUOrder → MergeMem → base_memory → Proj (again)&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Root Cause Summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Root cause&lt;/strong&gt;: In OpenJDK 8u442 on AArch64, C2's dead-code elimination can create a data-flow cycle:&lt;/p&gt;

&lt;p&gt;Proj → MemBarCPUOrder → MergeMem → base_memory → same Proj&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trigger&lt;/strong&gt;: org.springframework.core.MethodParameter.getParameterType() — a common Spring method that combines volatile accesses and potential object creation in a hot path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why 8u442&lt;/strong&gt;: The upstream fix was not backported into this update.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Production Workaround (Verified)
&lt;/h2&gt;

&lt;p&gt;For teams that cannot rebuild the JDK:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-XX:CompileCommand=exclude,org.springframework.core.MethodParameter::getParameterType&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This keeps the method at C1/interpreted level and avoids triggering the C2 bug. Performance impact is negligible because getParameterType is not a hot path in most Spring applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Full GDB Command Sequence
&lt;/h2&gt;

&lt;p&gt;For reference, here is the complete GDB workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Find container PID on host&lt;/span&gt;
docker inspect &amp;lt;container&amp;gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.State.Pid}}'&lt;/span&gt;

&lt;span class="c"&gt;# 2. Allow ptrace (if needed)&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;0 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /proc/sys/kernel/yama/ptrace_scope

&lt;span class="c"&gt;# 3. Capture core&lt;/span&gt;
gcore &lt;span class="nt"&gt;-o&lt;/span&gt; /data/coredump/hang &amp;lt;PID&amp;gt;

&lt;span class="c"&gt;# 4. Analyze with GDB&lt;/span&gt;
gdb &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-ex&lt;/span&gt; &lt;span class="s2"&gt;"set sysroot /proc/&amp;lt;PID&amp;gt;/root"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-ex&lt;/span&gt; &lt;span class="s2"&gt;"thread apply all bt 3"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-ex&lt;/span&gt; &lt;span class="s2"&gt;"quit"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /proc/&amp;lt;PID&amp;gt;/root/&amp;lt;path-to-jdk&amp;gt;/bin/java &lt;span class="se"&gt;\&lt;/span&gt;
  /data/coredump/hang.&amp;lt;PID&amp;gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/bt.txt 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Key Findings&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Root cause: C2's Ideal Graph can form a cycle when dead code elimination removes nodes in the object allocation path.&lt;/li&gt;
&lt;li&gt;Architecture specificity: The bug only manifests on AArch64 because only weak memory models require the MemBarCPUOrder nodes.&lt;/li&gt;
&lt;li&gt;Trigger: org.springframework.core.MethodParameter.getParameterType().&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Lessons Learned&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flame graphs are the first line of defense.&lt;/li&gt;
&lt;li&gt;Container debugging requires creative tooling — host gcore + sysroot works where in-container debugging fails.&lt;/li&gt;
&lt;li&gt;When debugging intermittent JVM issues, capture core dumps early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Final Recommendation&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;If you run Spring Boot on AArch64 and see unexplained 100% CPU from C2 CompilerThread:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Profile with async-profiler to confirm it's in can_see_stored_value&lt;/li&gt;
&lt;li&gt;Add the exclusion: &lt;code&gt;-XX:CompileCommand=exclude,org.springframework.core.MethodParameter::getParameterType&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Capture a core dump and verify the cycle using CLHSDB&lt;/li&gt;
&lt;li&gt;Report to your JDK vendor with the evidence&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Acknowledgments
&lt;/h2&gt;

&lt;p&gt;The method for extracting ciMethod information from core dumps was adapted from Vladimir Sitnikov's excellent 2018 article on analyzing stuck C2 compilations.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This investigation was conducted on OpenJDK 8u442 running on AArch64 processors.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: java, jvm, debugging, performance, aarch64&lt;/p&gt;

</description>
      <category>java</category>
      <category>jvm</category>
      <category>performance</category>
      <category>aarch64</category>
    </item>
  </channel>
</rss>
