skip to content

How does JMeter's one-thread-per-virtual-user model shape the choice against a non-blocking load runner?

level: seniorimportance: should knowfreq 51%

answer

  1. Ask what a virtual user physically is
  2. A user is a real Java thread
  3. Blocked while a response is outstanding
  4. No virtual threads at 6.0.0
  5. Scale by adding injectors, not a field

basics

~20 s

JMeter 6.0.0 starts one real Java platform thread per virtual user, so user count is an injector resource decision rather than a free configuration number, and you buy more concurrency by adding injectors instead of raising a field.

solid answer

~60 s

In Apache JMeter 6.0.0 a virtual user is an operating-system thread: the thread group creates a `java.lang.Thread` per user and that thread runs the user's whole iteration, blocking while a response is outstanding. JMeter 6.0.0 does not place virtual users on Java virtual threads, so the model holds even on Java 21. Two consequences drive the tool choice: - **Concurrency has a per-user price on the injector.** Each user carries a thread stack and a share of scheduling, so the reachable user count is a property of your injector and your plan, not a number you are free to set. - **Scaling is horizontal.** When one injector runs out, JMeter's answer is more injectors driven from a controller, not a bigger number in one field. That is precisely the axis on which runners that multiplex many users onto a small worker pool are compared with JMeter. State it as a fact about JMeter's execution model and let the other side's runtime be described by the tool that owns it.

go deeper

for a junior

Remember the core fact: in JMeter each virtual user is a real Java thread, so the number of users you configure has a direct cost on the machine running the test.

for a middle

Explain that the thread blocks while a response is outstanding, and that JMeter 6.0.0 does not move users onto Java virtual threads even on Java 21.

for a senior

Show that you size the user count against the injector rather than assuming it, and that you scale by adding injectors while checking the injector is not itself the limit.

for a principal

Judge when this cost is decisive at all. Weigh extra injector capacity against the cost of moving a working suite to a different runner, and say which one your team should buy.

## The mechanism, stated exactly Apache JMeter 6.0.0's stock thread group starts each virtual user by constructing a `java.lang.Thread` around that user's `JMeterThread` and starting it. The core `Open Model Thread Group` submits its users to a cached thread pool instead, but that pool still hands every running user its own platform thread, so no two concurrently active users ever share a carrier; and 6.0.0 uses no Java virtual threads anywhere in its execution path. On Java 17 or Java 21 alike, **one running virtual user is one platform thread**. That thread executes the user's samplers in order and is blocked for the duration of each outstanding response. Everything in this comparison follows from that one sentence, so get it right before reasoning further. ## What it costs, and what it does not - **It costs stack and scheduling, per user.** Every user owns a thread stack and competes for CPU in the operating system scheduler. Raising the user count raises injector cost in a way that does not flatten out. - **It does not cost you correctness.** A blocking, thread-per-user model produces perfectly valid load. The trade is efficiency of the generator, not fidelity of the test. - **It makes user count a resource decision.** The maximum you can drive from one injector depends on the plan the users run and the machine underneath, so it is sized, never assumed. What that sizing exercise looks like, and what a plan choice costs the injector, are covered by the JMeter tree's own execution topics and by the performance-testing foundations; do not answer this question with a memorised number. - **It sets the scaling direction.** JMeter's built-in answer to "more users than one box will carry" is more injectors under one controller — the distributed mode this tree covers separately. ## Why this is the sharpest axis in the comparison A runner built on a non-blocking runtime places many virtual users on a small number of workers, so an idle user waiting on a response occupies almost nothing. That is the structural difference behind every "JMeter is heavy" claim. The disciplined way to hold this argument is: 1. **State JMeter's side as fact.** One platform thread per user, blocking, no virtual threads at 6.0.0. 2. **State the consequence for you.** Injector count and injector sizing become part of the test design. 3. **Cede the other side by name.** Goroutine-backed virtual users belong to `dev-k6-runtime-js-runtime`, and an event-loop simulation engine with no thread per user belongs to the Gatling topics that own it, including `dev-gatling-delivery-peer-boundaries`. Do not narrate a runtime this topic does not own. ## When the model is not the deciding factor The efficiency gap only bills you when it bites, and often it does not: - **Modest concurrency.** Many real suites never approach the point where the model matters, and then the argument is decided by protocol scope, authoring skills and pipeline needs instead. - **Injectors are cheap relative to the system under test.** If a second injector container costs less than a week of migration work, the thread model is a line item, not a verdict. - **The bottleneck is the plan, not the threads.** Heavy per-sample work on the injector can dominate long before thread count does, which is a JMeter-side problem no rival runtime fixes for you. ## The interview answer Lead with the mechanism, not the folklore. Say that JMeter starts a real thread per virtual user and blocks it on each response; conclude that the user count is sized against the injector and scaled by adding injectors; then say plainly when that cost is and is not decisive. A candidate who claims a fixed maximum user count per machine, or who says JMeter switched its users to virtual threads, has invented the part that matters most.

  • Does running JMeter 6.0.0 on Java 21 put virtual users on virtual threads?
    No. JMeter 6.0.0 constructs a platform thread per virtual user regardless of the JDK it runs on, so a Java 21 runtime brings no change to the execution model. Java 21 is recommended for other reasons, but a candidate who claims virtual-thread users at 6.0.0 is describing a version that does not exist.
  • If one injector cannot carry the user count you need, what does JMeter offer?
    Horizontal scale: several JMeter engines running the plan, driven from one controller, with results returned to it. The mechanics of the controller and engine roles and what result collection costs are covered by this tree's distributed-execution topics; the point for tool choice is that the answer is more machines, not a larger number in one field.
  • How would you tell that the injector, not the system under test, has become the limit?
    By instrumenting the injector itself rather than only the system under test — its CPU, its garbage-collection pauses and whether it delivered the load the plan asked for. Reading those symptoms is covered by this tree's injector-overload topic and by the performance-testing foundations; the point here is only that a thread-model argument is worthless until you have checked it.

saying these in an interview costs you the question

  • Quotes a fixed maximum number of JMeter threads per machine as universal
  • Claims JMeter 6.0.0 runs virtual users on Java virtual threads
  • Says a thread-per-user generator produces invalid load rather than a costlier one
  • Assumes raising the thread count field is free once the plan is written
  • Decides the tool choice on the thread model alone, whatever the concurrency needed