<?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: Felipe Maschio</title>
    <description>The latest articles on DEV Community by Felipe Maschio (@maschiojv).</description>
    <link>https://dev.to/maschiojv</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%2F4024535%2Fb52b2b1d-7735-425f-9648-49cb6fc2c73d.jpg</url>
      <title>DEV Community: Felipe Maschio</title>
      <link>https://dev.to/maschiojv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maschiojv"/>
    <language>en</language>
    <item>
      <title>I made a Claude Code plugin that turns your project into a demo video</title>
      <dc:creator>Felipe Maschio</dc:creator>
      <pubDate>Tue, 21 Jul 2026 21:37:41 +0000</pubDate>
      <link>https://dev.to/maschiojv/i-made-a-claude-code-plugin-that-turns-your-project-into-a-demo-video-3735</link>
      <guid>https://dev.to/maschiojv/i-made-a-claude-code-plugin-that-turns-your-project-into-a-demo-video-3735</guid>
      <description>&lt;p&gt;You know how much work goes into making a decent product demo. I built a Claude Code plugin that does most of it for you.&lt;/p&gt;

&lt;p&gt;Because it runs inside Claude Code, it already knows your project. It can identify the main features, run the app and build the film with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/product-film
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a single HTML file, and that file is the film. Open it in Chrome and you'll get a start screen with one card for each cut. I usually ask for a full demo and a shorter loop for Product Hunt. Each card has its own &lt;code&gt;▶ Watch&lt;/code&gt; and &lt;code&gt;● Record&lt;/code&gt; buttons, and there's a theme-music picker that applies to whichever cut you record.&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%2Fksxqcr5aaet3g8dggfb7.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%2Fksxqcr5aaet3g8dggfb7.png" alt="The start screen of a generated film: two cards, Full cut and Short loop, each with its own Watch and Record buttons, and a theme-music picker below them." width="799" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit Record and the film captures itself from beginning to end, then drops an MP4 into your downloads. Firefox exports WebM. There's no screen recorder, OBS, editor or timeline involved.&lt;/p&gt;

&lt;p&gt;A few tracks are included, and you can upload your own audio. A friend discovered it doesn't even have to be music: he recorded a narration, uploaded the MP3 and used that instead.&lt;/p&gt;

&lt;p&gt;The bare command is enough, but you can also direct the film in the same message: which flow to show, what to leave out and which screen deserves more time. Since it's still a conversation with Claude Code, the first cut doesn't have to be the final one. You can say, "Drop the pricing scene and hold longer on the deadlock," and it'll generate a new version. You only hit Record when you're happy with it.&lt;/p&gt;

&lt;p&gt;If you want to see the result, &lt;a href="https://threadmine.dev/en#video-demo" rel="noopener noreferrer"&gt;the demo on ThreadMine's landing page&lt;/a&gt; was made entirely with the plugin. The short loop plays on the page, and clicking it opens the full version with music.&lt;/p&gt;

&lt;p&gt;The biggest advantage over AI video generators is that product-film doesn't have to guess what your application looks like. Those tools can make slick videos, but getting them to reproduce a coherent sequence of your actual screens is another matter. You either accept a slightly fictional version of the UI or spend a lot of time feeding them screenshots and correcting the details when they drift.&lt;/p&gt;

&lt;p&gt;Recording yourself clicking through the app works, but the result usually still needs editing. Hiring an editor costs money and takes time. Claude Code already understands the project, so it can skip most of that setup.&lt;/p&gt;

&lt;p&gt;It also doesn't upload your application or captured screens to a separate video service. The plugin works from a real run of your app on your machine.&lt;/p&gt;

&lt;p&gt;In my case, the app is ThreadMine, a thread-dump analyzer. The demo shows a dump being genuinely analyzed: health score F, 30 out of 100, with a real deadlock where &lt;code&gt;DeadLockThread1&lt;/code&gt; waits on &lt;code&gt;DeadLockThread2&lt;/code&gt;, which waits on &lt;code&gt;DeadLockThread1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I didn't choose 30 because it looked dramatic. That's the number the engine returned. Paste the same dump into the live tool and you'll get the same F 30. The video can't show a result the product didn't actually produce.&lt;/p&gt;

&lt;p&gt;Before creating a single frame, the plugin runs the real application with Playwright, clicks through the actual flow and captures the real screens and values. That's the source of truth. The film is then choreographed on top of it: a cursor easing into buttons, captions appearing after each action and a slow push-in on whatever deserves attention.&lt;/p&gt;

&lt;p&gt;Every visual is a pure function of &lt;code&gt;t&lt;/code&gt;. That means the film can seek to any point, verify itself beat by beat and record a take that begins and ends exactly where it should.&lt;/p&gt;

&lt;p&gt;It's open source and MIT licensed. Installation takes two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add alliah-tech/product-film
/plugin install product-film@product-film-marketplace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One practical tip: open &lt;code&gt;/plugin&lt;/code&gt;, go to Marketplaces and enable auto-update. Claude Code doesn't turn it on by default for third-party marketplaces, and I'm still releasing updates.&lt;/p&gt;

&lt;p&gt;I'd also run it with the most capable model you have and set effort to high. The difficult part isn't writing the code. It's reproducing the real interface without deciding to "improve" it along the way. Weaker models sometimes get creative and prettify parts of the UI, which breaks the one rule that keeps the result from becoming AI slop.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Felipe. I built &lt;a href="https://threadmine.dev" rel="noopener noreferrer"&gt;ThreadMine&lt;/a&gt;, the analyzer used in the example, and &lt;a href="https://github.com/alliah-tech/product-film" rel="noopener noreferrer"&gt;product-film&lt;/a&gt;, the plugin. Happy to answer questions in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Your Loom App Quietly Became a Thread Pool Again: A Field Guide to Virtual Thread Pinning</title>
      <dc:creator>Felipe Maschio</dc:creator>
      <pubDate>Fri, 10 Jul 2026 21:54:18 +0000</pubDate>
      <link>https://dev.to/maschiojv/your-loom-app-quietly-became-a-thread-pool-again-a-field-guide-to-virtual-thread-pinning-2a3f</link>
      <guid>https://dev.to/maschiojv/your-loom-app-quietly-became-a-thread-pool-again-a-field-guide-to-virtual-thread-pinning-2a3f</guid>
      <description>&lt;p&gt;The incident that taught me to respect pinning looked like nothing. A service freshly migrated to virtual threads, a load test that plateaued at about 420 requests per second no matter how much traffic we threw at it, CPU sitting at 9%, zero errors, zero warnings, nothing in the logs. The machine had 8 cores, and the one downstream HTTP call in the hot path took about 19 ms. Do the arithmetic: 8 × (1000 / 19) ≈ 421.&lt;/p&gt;

&lt;p&gt;The service that was supposed to scale to millions of virtual threads was serving exactly one request per CPU core. Loom had quietly handed us back a bounded thread pool, and the code looked perfectly innocent. That failure mode has a name — &lt;strong&gt;pinning&lt;/strong&gt; — and this is the field guide I wish I'd had that night: what it is, the two (and only two) things that cause it, what JDK 24 changed, and how to catch it before your throughput graph does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pinning actually is
&lt;/h2&gt;

&lt;p&gt;A virtual thread doesn't own an OS thread. It runs on a small pool of platform threads called &lt;strong&gt;carrier threads&lt;/strong&gt; — concretely, the workers of a dedicated &lt;code&gt;ForkJoinPool&lt;/code&gt; living in a thread group named &lt;code&gt;CarrierThreads&lt;/code&gt;, with default parallelism equal to &lt;code&gt;Runtime.availableProcessors()&lt;/code&gt;. When a virtual thread blocks — on I/O, a lock, a queue — it normally &lt;strong&gt;unmounts&lt;/strong&gt;: it saves its stack, steps off the carrier, and frees that carrier to run another virtual thread. That unmount is the entire trick that lets a handful of OS threads serve millions of virtual ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pinning is when the unmount can't happen.&lt;/strong&gt; The virtual thread blocks but stays mounted, and its carrier sits there doing nothing useful for the whole duration. One pinned carrier is a rounding error. But the default carrier pool is only as big as your core count, so if a hot path pins routinely, you pin &lt;em&gt;every&lt;/em&gt; carrier at once — and then no virtual thread anywhere makes progress. That's not a slowdown; it's scheduler starvation, and from the outside it looks a lot like a deadlock. You can raise the ceiling with &lt;code&gt;-Djdk.virtualThreadScheduler.parallelism=N&lt;/code&gt;, but that only delays the moment of exhaustion. It doesn't fix anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two causes — and it really is just two
&lt;/h2&gt;

&lt;p&gt;There are exactly two situations where the JVM cannot unmount a blocked virtual thread:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Blocking inside &lt;code&gt;synchronized&lt;/code&gt; (JDK 21 through 23).&lt;/strong&gt; Up to and including JDK 23, an object monitor is tied to the carrier thread that entered it. If a virtual thread blocks — or calls &lt;code&gt;Object.wait()&lt;/code&gt; — while holding a monitor, the JVM can't move it off the carrier without breaking monitor ownership, so it pins. This is by far the most common cause in real code, because a blocking call buried inside a &lt;code&gt;synchronized&lt;/code&gt; method is trivial to write and invisible at the call site. And the monitor doesn't have to be &lt;em&gt;yours&lt;/em&gt;: &lt;code&gt;synchronized&lt;/code&gt; inside a library, or inside the JDK itself, pins exactly the same way. &lt;code&gt;ConcurrentHashMap.computeIfAbsent&lt;/code&gt; runs your mapping function under an internal bin lock — put a blocking call inside it and you've pinned a carrier without a single &lt;code&gt;synchronized&lt;/code&gt; keyword in your own code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Native frames.&lt;/strong&gt; When a virtual thread has a native method (JNI) or a foreign downcall (the Foreign Function &amp;amp; Memory API) on its stack and it blocks, the JVM can't capture and restore the native frame, so it pins. This one has no &lt;code&gt;synchronized&lt;/code&gt; to blame — and it is &lt;em&gt;not&lt;/em&gt; fixed by JDK 24. It also hides in a place nobody expects: class initialization runs through native frames, so a blocking call inside a static initializer pins even on the newest JDKs.&lt;/p&gt;

&lt;p&gt;Just as important is what's &lt;strong&gt;not&lt;/strong&gt; on the list: ordinary blocking I/O through the JDK (&lt;code&gt;Socket&lt;/code&gt;, &lt;code&gt;InputStream&lt;/code&gt;, &lt;code&gt;Files&lt;/code&gt;), &lt;code&gt;BlockingQueue&lt;/code&gt;, &lt;code&gt;ReentrantLock&lt;/code&gt;, &lt;code&gt;CompletableFuture&lt;/code&gt;, &lt;code&gt;Thread.sleep()&lt;/code&gt; — all of it was re-plumbed for Loom and unmounts cleanly. Pinning is a short, specific list, which is exactly why it's detectable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The canonical bug
&lt;/h2&gt;

&lt;p&gt;Nearly every real pin I've read in a dump is some flavor of a cache or rate limiter guarding a slow call with &lt;code&gt;synchronized&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PriceService&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;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cache&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;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Looks harmless. On JDK 21-23 it pins the carrier for the whole HTTP call.&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;synchronized&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;computeIfAbsent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;-- blocks while holding the monitor&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;Every cache miss blocks on the network &lt;em&gt;while holding the monitor&lt;/em&gt;. On JDK 21–23 that virtual thread pins its carrier for the entire round trip. Run a few hundred concurrent requests and you've pinned every carrier; the rest of the workload queues behind a monitor that never unmounts. That's my 420-requests-per-second incident in five lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  What JDK 24 changed (JEP 491)
&lt;/h2&gt;

&lt;p&gt;JDK 24 shipped &lt;a href="https://openjdk.org/jeps/491" rel="noopener noreferrer"&gt;JEP 491, "Synchronize Virtual Threads without Pinning"&lt;/a&gt;. It reworked monitor ownership so the monitor is associated with the virtual thread itself rather than its carrier — which means a virtual thread &lt;em&gt;can&lt;/em&gt; now unmount while blocked inside &lt;code&gt;synchronized&lt;/code&gt;, while waiting to enter one, or while parked in &lt;code&gt;Object.wait()&lt;/code&gt;. The most common cause of pinning simply goes away on JDK 24+, with no code change.&lt;/p&gt;

&lt;p&gt;Two practical consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On JDK 24+, the only remaining pins come from native frames — JNI, FFM downcalls, and class initialization.&lt;/li&gt;
&lt;li&gt;The old detection flag &lt;code&gt;-Djdk.tracePinnedThreads&lt;/code&gt; was &lt;strong&gt;removed&lt;/strong&gt; in JDK 24. Don't ship runbooks that depend on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're on JDK 21–23, though, &lt;code&gt;synchronized&lt;/code&gt; pinning is very much alive, and upgrading is often the single cleanest fix you can make.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to catch it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On JDK 21–23 — the legacy flag.&lt;/strong&gt; Run with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-Djdk&lt;/span&gt;.tracePinnedThreads&lt;span class="o"&gt;=&lt;/span&gt;full &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JVM prints a stack trace every time a virtual thread pins, and the frame annotated &lt;code&gt;&amp;lt;== monitors:1&lt;/code&gt; is the culprit. That one line is the whole diagnosis. Just remember this flag no longer exists on JDK 24.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everywhere — JFR.&lt;/strong&gt; Since JDK 21 the JVM emits a &lt;code&gt;jdk.VirtualThreadPinned&lt;/code&gt; Flight Recorder event when a virtual thread blocks while pinned. It's enabled by default — but with a &lt;strong&gt;20 ms threshold&lt;/strong&gt;, so short pins are invisible unless you lower it. In JDK 24 the event got better: it's emitted for every pinning occurrence and carries the reason and the carrier's identity. Since native-frame pins still fire it, this is the detection you should wire into production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-XX&lt;/span&gt;:StartFlightRecording&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rec.jfr,settings&lt;span class="o"&gt;=&lt;/span&gt;profile &lt;span class="nt"&gt;-jar&lt;/span&gt; app.jar
jfr print &lt;span class="nt"&gt;--events&lt;/span&gt; jdk.VirtualThreadPinned rec.jfr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;From a thread dump.&lt;/strong&gt; Plain &lt;code&gt;jstack&lt;/code&gt; won't show you virtual threads at all. Use the virtual-thread-aware dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jcmd &amp;lt;pid&amp;gt; Thread.dump_to_file &lt;span class="nt"&gt;-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json dump.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It lists the carrier threads and the virtual thread mounted on each. A carrier in the &lt;code&gt;CarrierThreads&lt;/code&gt; group that is blocked while its mounted virtual thread sits in a &lt;code&gt;synchronized&lt;/code&gt; frame (or a native frame) is the visual signature of a pin. Count how many carriers show it versus your pool size — that ratio tells you how close you are to full starvation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Swap &lt;code&gt;synchronized&lt;/code&gt; for &lt;code&gt;ReentrantLock&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;java.util.concurrent.locks.ReentrantLock&lt;/code&gt; is Loom-aware: a virtual thread that blocks on it, or while holding it, unmounts cleanly. This is the direct, version-independent fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgrade to JDK 24+.&lt;/strong&gt; JEP 491 removes the &lt;code&gt;synchronized&lt;/code&gt; pin entirely. Native-frame pins remain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't hold a lock across an external call.&lt;/strong&gt; Often the honest fix is structural: compute the value outside the critical section and only lock the map update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For native/FFM pins, isolate the path.&lt;/strong&gt; Run unavoidable blocking native calls on a dedicated platform-thread executor, or size the carrier pool so a few concurrent pins can't starve everything.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the rewrite of the example. One trap to avoid: don't just move the blocking call into &lt;code&gt;ConcurrentHashMap.computeIfAbsent&lt;/code&gt; — as noted above, its mapping function runs under an internal bin lock, and on JDK 21–23 you'd have rebuilt the same pin one layer down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PriceService&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;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;cache&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;ConcurrentHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;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;ReentrantLock&lt;/span&gt; &lt;span class="n"&gt;lock&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;ReentrantLock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lock&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                       &lt;span class="c1"&gt;// Loom-aware: unmounts if it blocks&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// re-check under the lock&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// blocks; carrier is freed&lt;/span&gt;
            &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="o"&gt;;&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;lock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;unlock&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;This no longer pins anywhere — though it still serializes cache misses behind one lock, which is fix #3's territory: the &lt;em&gt;next&lt;/em&gt; refinement is not holding any lock across the network call at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I ended up automating the read
&lt;/h2&gt;

&lt;p&gt;Doing this analysis by hand — turn on a flag, reproduce, dump, find the carriers, match frames — is fine once. It's tedious by the tenth incident, and worse, half the tooling depends on remembering to enable something &lt;em&gt;before&lt;/em&gt; the problem happens. So I built a tool that does the read on any thread dump you give it: it finds the carriers, checks what's mounted on each, flags the pinned ones with the offending frame, and reports pinned-carriers-versus-pool-size — the number that tells you whether you're one bad path away from starvation. It's &lt;a href="https://threadmine.dev/en/analyze" rel="noopener noreferrer"&gt;ThreadMine&lt;/a&gt;; the web analyzer is free and takes a dump with no signup. Full disclosure: it's my project — I got tired of reading dumps by hand, so I automated the part I kept repeating. And a fair caveat: a dump is a snapshot, so for intermittent pinning, JFR is still the better signal.&lt;/p&gt;

&lt;p&gt;If you want the deeper reference — carriers, JEP 491, the full detection matrix — I keep it updated here: &lt;a href="https://threadmine.dev/en/resources/virtual-thread-pinning" rel="noopener noreferrer"&gt;virtual thread pinning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pinning is the one Loom failure mode that cancels your scalability story without a single error in the logs. The rules are short: only &lt;code&gt;synchronized&lt;/code&gt; (pre-JDK 24) and native frames pin; detect with &lt;code&gt;jdk.tracePinnedThreads&lt;/code&gt; on 21–23 and the &lt;code&gt;jdk.VirtualThreadPinned&lt;/code&gt; JFR event everywhere; fix with &lt;code&gt;ReentrantLock&lt;/code&gt;, an upgrade, or by not holding locks across slow calls. Know the shape, and it stops being invisible.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Felipe Maschio is the founder of &lt;a href="https://threadmine.dev/en" rel="noopener noreferrer"&gt;ThreadMine&lt;/a&gt;, a free JVM thread dump analyzer that detects deadlocks, thread leaks, pool exhaustion, CPU spikes and virtual thread pinning.&lt;/em&gt;&lt;/p&gt;

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