-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture and Telemetry
This document details the internal architecture of LoomDoctor and the technical mechanics of Java 21+ Virtual Thread concurrency.
Virtual threads (java.lang.Thread.ofVirtual()) are user-mode threads managed directly by the Java Virtual Machine rather than the host operating system.
+--------------------------------------------------------------------+
| Virtual Threads (M) |
| [VT 1] [VT 2] [VT 3] [VT 4] ... [VT 10,000] ... [VT 1,000,000]
+--------------------------------------------------------------------+
| (Mount / Unmount)
v
+--------------------------------------------------------------------+
| ForkJoinPool Carrier Threads (N) |
| [Worker 1] [Worker 2] [Worker 3] |
+--------------------------------------------------------------------+
| (1:1 mapping)
v
+--------------------------------------------------------------------+
| OS Kernel Threads (N) |
+--------------------------------------------------------------------+
-
M : N Scheduling: Millions of virtual threads (
$M$ ) are multiplexed over a small pool of OS carrier threads ($N$ , typically matchingRuntime.getRuntime().availableProcessors()). -
Non-blocking Unmount: When a virtual thread performs standard Java I/O (e.g.,
Socket.read(),Thread.sleep()), the JVM unmounts the virtual thread, preserving its stack in Java heap memory and freeing the carrier thread to execute another task.
A virtual thread is said to be pinned to its carrier thread when it cannot be unmounted during a blocking operation. This occurs under two primary circumstances:
-
Synchronized Blocks & Methods: The virtual thread enters a monitor lock (
synchronized (lock) { ... }) and then executes a blocking call (such as JDBC I/O or network calls). The current JVM implementation cannot unmount virtual threads holding object monitors. - Native Calls: When executing blocking operations inside JNI or foreign functions (Project Panama FFM).
When a carrier thread is pinned:
- The carrier thread remains occupied waiting for the blocking call to finish.
- Other ready virtual threads waiting in the
ForkJoinPoolqueue cannot make progress on that carrier thread. - If all carrier threads become pinned simultaneously, the application experiences complete throughput collapse.
LoomDoctor monitors pinning using a thread-safe circular buffer:
- Captures the pinned carrier thread name (
ForkJoinPool-1-worker-3), the virtual thread descriptor, the duration in milliseconds, and the exact source code location. - Provides event callbacks (
PinningListener) to integrate with enterprise alerting or log aggregators.
Before virtual threads, platform thread pools (e.g., 200 Tomcat worker threads) acted as a natural concurrency throttle. With virtual threads, an application can effortlessly accept 100,000 simultaneous incoming HTTP requests.
However, if each request requires a database connection from a fixed-size pool (e.g., HikariCP with 20 connections):
- 20 virtual threads acquire connections.
- 99,980 virtual threads queue in memory waiting for a connection lease.
- Queue wait times spike exponentially, causing connection timeouts, cascading circuit-breaker trips, and client-side HTTP 504 Gateway Timeouts.
LoomDoctor evaluates pool congestion using the queue-to-capacity ratio:
- When
$\text{Wait Ratio} \ge \text{threshold}$ (default:2.0), aStarvationAlertis triggered, andLoomDoctor.diagnose()elevates system health toCRITICAL.
-
Before (Causes Pinning):
public synchronized byte[] fetchExternalData() { return httpClient.send(...); // Pinned to carrier thread! }
-
After (Yields Cleanly):
private final ReentrantLock lock = new ReentrantLock(); public byte[] fetchExternalData() { lock.lock(); try { return httpClient.send(...); // Virtual thread unmounts cleanly } finally { lock.unlock(); } }
Rather than allowing unbounded virtual threads to bombard connection pools, protect downstream resources with a java.util.concurrent.Semaphore:
private final Semaphore dbPermits = new Semaphore(50);
public Order processOrder(OrderRequest req) {
dbPermits.acquire();
try {
return orderRepository.save(req);
} finally {
dbPermits.release();
}
}LoomDoctor • Virtual Thread Pinning Diagnostics & Carrier Starvation Telemetry for Java 21+ • GitHub