Quasar Fibers: Minimal Architecture for Massively Concurrent Java Without OS Threads
Quasar fibers enable many logical tasks on few carrier threads via load-time bytecode instrumentation. This note covers minimal setup, shared-memory boundaries, and operational checks.
26 Dec 2025, 10:52 UTC

Problem: millions of concurrent tasks on a small thread pool
Java applications that need high concurrency with blocking I/O typically pay for each logical task with an OS thread. Quasar offers fiber-based lightweight threading by instrumenting bytecode at load time so blocking calls become cooperative suspension points. The useful takeaway is that you get many logical strands on few carrier threads, but only if the agent instruments classes before they are used and you respect shared-memory boundaries.
Requirements for fiber suspension
A fiber is a Quasar Strand that can be suspended and resumed by the FiberScheduler. Suspension requires bytecode rewriting of blocking JDK APIs and of user code marked @Suspendable.
Essential requirements:
- Quasar core agent attached as
-javaagent:quasar-core.jarbefore any blocking class loads. - Quasar library on the application classpath.
- Entry points annotated
@Suspendableor started viaFiber.start.
The agent rewrites methods such as java.net.SocketInputStream.read and java.util.concurrent.locks.Lock.lock to yield the current fiber to the scheduler instead of blocking the carrier thread.
Smallest suitable design
A minimal setup keeps existing synchronous code style and adds the agent and a fiber entry point.
Build dependency example:
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
</dependency>
JVM launch, run as the service owner with permission to read the agent jar:
java -javaagent:/opt/quasar/quasar-core.jar -jar app.jar
Risk: Late attachment leaves already loaded classes uninstrumented, causing carrier-thread blocks.
Starting a fiber:
import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.Suspendable;
public class Worker {
@Suspendable
public void runTask() { /* blocking I/O allowed */ }
public static void main(String[] args) {
Fiber.start(() -> new Worker().runTask());
}
}
No ExecutorService wrapping is needed for pure fiber workloads. Mixing raw ExecutorService threads with fibers without proper wrapping can leak carrier threads.
Trust and data boundaries
Fibers share the ordinary Java heap and the stack of their carrier thread. There is no isolation between fibers.
Data boundaries remain the Java Memory Model. Standard synchronization, volatile fields, and concurrent collections are required for shared state. Quasar provides Strand-local primitives for fiber-local data, but they do not create memory isolation.
Trust boundary: the agent rewrites bytecode globally. Any code loaded after the agent starts is subject to rewriting, which can interact with other bytecode manipulators.
Operational checks
Health checks focus on agent presence, instrumentation, and scheduler behavior.
- Agent initialization log: On startup look for the log line indicating Quasar Agent initialized. Run with
-Dquasar.debug=trueto see instrumentation messages such asInstrumented java.net.SocketInputStream.read. - Scheduler metrics: Expose
FiberScheduleractive fiber count and scheduled task count via your metrics system. - Thread dump inspection: Run
jcmd <pid> Thread.printas an operator with permission to signal the JVM. Carrier threads should show short stacks fromFiberSchedulerrather than deep application call stacks during blocking operations, indicating suspension is working.
A practical verification pattern is to submit many fibers that increment a shared AtomicInteger and observe that the active OS thread count stays low while the counter reaches the expected total.
Failure modes
Uninstrumented native or JNI calls block the carrier thread and stall other fibers scheduled on it.
Excessive fiber creation can exhaust heap because each fiber allocates a continuation. Backpressure or pooling is needed.
Bytecode clashes with other agents such as certain AOP proxies or Mockito can corrupt rewrites, leading to hangs or ClassCast issues. Load order matters.
Conditions that would change the design
Move away from Quasar fibers when runtime instrumentation is unavailable, e.g., GraalVM native image where load-time agent rewriting is not reliable.
Adopt a platform with built-in lightweight threads such as Project Loom Virtual Threads when you can target a JVM that provides them, reducing operational complexity.
Hard real-time latency requirements may make fiber scheduling latency unacceptable; a design change to dedicated OS threads or an RTOS is then appropriate.
Limitations: fibers are cooperative only at instrumented points. Code that performs blocking work outside instrumented APIs will block carriers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.