In JMeter, what does each extra Thread Group thread allocate before it sends a request?
answer
- Cost scales with the plan, not the request
- Every thread owns a private copy
- Listeners are the documented exception
- Multiply element count by thread count
basics
~10 sOne real platform thread, its own clone of everything under that Thread Group, and its own copy of the variables. Plan size therefore multiplies by thread count before the first request goes out.
solid answer
~40 sStarting a thread runs three steps that scale with the *plan*, not with the request. JMeter calls `cloneTree(threadGroupTree)`, which walks the whole subtree under the Thread Group and clones every test element, so each thread owns its own samplers, controllers, timers, assertions and extractors. It then creates a real `java.lang.Thread` for that virtual user. Finally the thread gets its own `JMeterVariables`, populated by copying the context's entries in. The exception is elements marked `NoThreadClone` - among them the result collector behind a listener, the summariser, the backend listener, and the Counter and Random Variable config elements - which stay as one shared, thread-safe instance. The consequence is arithmetic: 200 elements under a Thread Group at 500 threads is roughly 100,000 element instances allocated during ramp-up, before any measurement begins.
code
java · 6 lines// org.apache.jmeter.threads.ThreadGroup#startNewThread (abridged)
JMeterThread jmThread = makeThread(engine, this, notifier, groupNumber, threadNum,
cloneTree(threadGroupTree), variables);
Thread newThread = new Thread(jmThread, jmThread.getThreadName());
registerStartedThread(jmThread, newThread);
newThread.start();go deeper
Remember the headline: a JMeter virtual user is a real thread, and the elements under the Thread Group are copied for each one. Do not expect a plan to be free just because it is not sampling yet.
Explain the start-up sequence - clone the subtree, create the thread, copy the variables - and name the NoThreadClone exception that keeps listeners shared instead of copied.
Read a plan with the multiplication in mind: spot the element count, the elements that carry state, and the include-style controllers that quietly expand the tree that gets cloned.
Set the conventions that keep plans small enough to run at the concurrency the team needs, and decide when a plan's shape, rather than the injector, is what has to change.
## What starting one thread actually does When a JMeter Thread Group starts, it loops over the requested number of threads and, for each one, does three things that all scale with the plan rather than with the request: ```java JMeterThread jmThread = makeThread(engine, this, notifier, groupNumber, threadNum, cloneTree(threadGroupTree), variables); // 1. clone the whole subtree Thread newThread = new Thread(jmThread, jmThread.getThreadName()); // 2. a real platform thread newThread.start(); ``` 1. **`cloneTree(threadGroupTree)`** — a `TreeCloner` walks everything under the Thread Group and calls `clone()` on each test element. Every sampler, controller, timer, assertion, extractor and config element in that subtree exists once *per thread*. 2. **`new Thread(...)`** — a real platform thread, started immediately (or after its ramp-up delay). JMeter's virtual users are operating-system threads; a plan asking for 500 concurrent users is asking for 500 live threads. 3. **Its own variables** — the thread's `JMeterVariables` is populated by copying in the entries the context holds, so each thread reads and writes its own map rather than sharing one. ## What is not cloned The exception is elements marked `NoThreadClone`, which the cloner deliberately leaves shared: the `ResultCollector` behind every file-writing listener, the `Summariser`, the `BackendListener`, the `ResultSaver` — and, outside the listeners, the `CounterConfig` and `RandomVariableConfig` config elements. That is why those classes are written to be thread-safe, and it flips their cost from *memory multiplied by threads* to *contention shared between threads*. Disabled elements are not cloned either — they are removed from the tree entirely when the plan is converted, before the first thread starts. ## Why the plan's size is a per-thread cost Put the two halves together and the arithmetic is uncomfortable. A tidy-looking plan with 200 elements under its Thread Group, run at 500 threads, instantiates roughly 100,000 test elements before the first request leaves the machine — and the allocation happens during ramp-up, when the run is also opening connections and starting threads. This is the reason the same advice keeps appearing in JMeter's own guidance on reducing resource use: - **Reuse one sampler in a loop** driven by data rather than twenty near-identical samplers, because twenty samplers is twenty clones per thread. - **Beware of elements that expand.** An include-style controller that pulls another file's elements into the plan adds all of them to the tree, so they are cloned per thread too. - **Delete what you are not using.** A leftover element you never look at is still cloned 500 times. - **Watch for elements that carry state**, such as a config element holding a large in-memory structure: cloned per thread, that structure is paid for per thread. ## What this tells you, and what it does not This is a statement about the shape of the cost, not about a number. How many threads one machine can carry, and how you would decide how many machines to budget, is generator-sizing reasoning that belongs to the performance-testing foundations, not to JMeter's element model. What JMeter's model tells you is *which lever moves it*: because the cost is `plan size x threads`, trimming the plan pays back multiplied by the thread count, while trimming a single expensive element pays back once per sample. It is also not specific to the stock Thread Group. JMeter's core thread groups run a virtual user on a real thread; a group that hands out threads from a pool still holds one live thread per user that is currently running, so *concurrent users* and *live threads* are the same number whichever group you pick. Load runners built on non-blocking runtimes make a different trade here — that contrast belongs to those tools' own topics, such as `dev-k6-runtime-js-runtime` and `dev-gatling-delivery-peer-boundaries` — but the JMeter-side fact is the one worth carrying into a plan review: every element you leave in the tree, you are buying once per user.
- Which JMeter elements are shared between threads instead of being cloned into each one?Those marked NoThreadClone. Among the listeners that is the result collector behind every file-writing listener, the summariser, the backend listener and the result saver - but the marker is not a listener privilege: the Counter and Random Variable config elements carry it too. They are all written to be thread-safe because one instance serves the whole plan. That flips their cost from memory multiplied by threads to contention shared between threads, which is why a listener hurts throughput rather than only heap.
- If a plan is too heavy per thread, what is the highest-leverage thing to change in it?Element count under the Thread Group, because it is multiplied by every thread. Reusing one sampler in a loop instead of many near-identical ones, and deleting elements nobody reads, pay back once per user. Trimming a single expensive element only pays back once per sample - useful, but a different order of saving.
saying these in an interview costs you the question
- Thinks all threads share one copy of the plan
- Says JMeter multiplexes many users onto few threads
- Ignores plan size because only samplers send requests
- Assumes disabled elements are still cloned per thread