Load Generator Sizing, JVM Tuning, OS Limits, and Network Capacity: Core Concepts and Mental Model
Chapter 25 proved that distributed load is the sum of multiple engines. That immediately creates the next engineering question: how much valid load can one engine actually sustain? A target can appear slow simply because JMeter is at 100% CPU, pausing for GC, churning TCP ports, waiting on DNS, blocking on disk writes or saturating its network interface. Generator health is therefore part of the experiment—not an implementation detail hidden behind the JMX.
Learning objectives
- Model configured workload as demand on finite generator resources.
- Define heap, GC, CPU saturation, file descriptors/handles, sockets, ephemeral ports, DNS cache, disk I/O and network throughput.
- Distinguish client elapsed time from target service time and timer delay.
- Inspect generator state before tuning it.
- Reject universal “threads per core/GB” sizing rules.
- Define usable generator capacity from achieved workload plus resource headroom.
1. The practical problem: the injector can saturate first
Suppose a plan is configured for 2,000 virtual users. The target reports low CPU and stable service time, but JMeter achieved RPS falls while the generator reaches 100% CPU and GC time rises. Declaring “the server saturated at this load” would be wrong: the injector failed to deliver the experiment.
127.0.0.1:8026. Normal
profile = 10 threads ×100 loops = 1,000 samples, 100 ms pacing, 8
KiB synthetic response, ≤20 seconds/run. No OS limits,
firewall/antivirus, DNS/JDK security or production settings are
changed; the lab uses no real credentials.
Never substitute a public/shared/production endpoint.
2. Mental model: plan cost + concurrency → generator resources → achieved load
A JMeter sample is generated by an injector that has finite CPU, heap, sockets, resolver, disk and network capacity. If one of those resources saturates, the configured workload and the achieved workload diverge even when the target itself is healthy.
flowchart TD P[Test-plan cost threads / timers / assertions / scripts / listeners] --> J[JMeter JVM] J --> H[Heap allocation + GC] J --> C[CPU scheduling] J --> S[Sockets / OS handles or FDs / local ports] J --> D[DNS resolution / caches] J --> N[Network interface + TCP] J --> R[Result serialization + JTL disk I/O] S --> T[HTTP target] D --> T N --> T T --> E[Response SampleResults] E --> J J --> A[Achieved samples / RPS / latency] G[Generator telemetry] --> V[Validity decision] A --> V T --> ST[Target service telemetry] ST --> V
The plan determines how many threads exist and how much work each completed sample performs inside the injector. JMeter allocates Java objects, schedules work on CPU, opens/reuses sockets, may resolve names, transfers request/response bytes and serializes selected result fields. The target responds, but the measured SampleResult includes the entire client-visible path. Generator telemetry and target service telemetry therefore have to be evaluated together before a latency/capacity conclusion is valid.
3. Core generator terms
| Term | Beginner definition | What failure looks like |
|---|---|---|
| Heap | Java-managed memory used by JMeter objects, samples, scripts, protocol state and listeners. | Old-gen/heap pressure, frequent/long GC, OOM, allocation stalls. |
| Garbage collection (GC) | JVM reclaims unreachable objects. | Rising GC time/pauses and less CPU time available for sampling. |
| CPU saturation | Generator cores have little scheduling headroom. | High process/system CPU, delayed timers/samplers, achieved RPS plateaus. |
| Handle / file descriptor | OS reference to files/sockets/etc.; Windows reports handles, POSIX systems expose FDs. | Open failures, socket/file errors, rising handle/FD count near a system/process limit. |
| Socket | One endpoint of a network connection. | Large established/TIME_WAIT sets, connect errors, local-port pressure. |
| Ephemeral/local port | Temporary local TCP source port used for outgoing connections. | Rapid connection churn consumes port space/TIME_WAIT state. |
| DNS | Hostname-to-address resolution path and caches. | Lookup delay/failure, different target IPs/caching behavior. |
| Disk/JTL I/O | Serialization/writes for result artifacts/logs. | Large files, write latency, full disk, post-run artifact bottleneck. |
| Network capacity | NIC/TCP bytes/packets/queues between injector and target. | NIC saturation, drops/retransmission/queueing, achieved throughput ceiling. |
4. Configured load is not achieved load
Configured load is Thread Groups, loops, timers/rates and target selection. Achieved load is the samples/concurrency/RPS the generator actually delivered after JVM/OS/network/result costs. Usable generator capacity is the highest controlled workload that meets your generator-headroom/validity criteria—not the largest number you can type into “Number of Threads.”
5. JMeter's current JVM launcher baseline
Current JMeter launcher defaults are 1 GiB heap and G1GC. JMeter's Getting Started guide explicitly says 1 GiB might not be enough; the correct heap depends on the plan and thread count. Do not read that as “always increase heap.” First determine whether live-set/GC pressure is the constraint.
Use a setenv.bat/setenv.sh or process
environment when a measured change is justified rather than editing
the distributed JMeter launcher scripts in place.
6. CPU cost depends on plan and target speed
A faster target can make the generator work harder because threads finish sooner and execute more samples per second. CPU cost also rises with regex/JSON/XML processing, uncached scripts, compression, TLS, large payload hashing, listeners and result serialization. A “20% CPU at 1,000 threads” rule from another test tells you little about this plan.
7. Connection reuse versus socket churn
JMeter defaults to HttpClient4. The HTTP Request Use KeepAlive option works with the HttpComponents implementation. Reuse usually means a virtual user can issue many requests over fewer TCP connections; disabling it forces connection churn, additional TCP handshakes and more local source ports/TIME_WAIT state.
The chapter checkpoint intentionally begins with keep-alive disabled so socket/ephemeral-port pressure is observable at a safe scale, then turns it back on as the reversible plan correction.
8. DNS is a separate performance dependency
By default JMeter uses JVM DNS caching. DNS Cache Manager can provide per-thread/per-iteration behavior independent of JVM/OS caches, but it works only with HttpClient4. Do not clear/disable global OS/JDK DNS caches during a capacity test unless that is explicitly part of the modeled production behavior.
The mandatory generator benchmark uses 127.0.0.1 to
remove DNS variability. DNS is inspected separately.
9. JTL is generator work
Every saved field must be serialized/written. Chapter 22 established the lean default: CLI, CSV, no bodies/request/response headers unless justified. For generator sizing, record JTL bytes per sample and disk free space so result output cannot silently become the limiting subsystem.
Preserve the matching jmeter.log for
every profile run. JTL records sample outcomes;
jmeter.log preserves engine/runtime/script/connection
warnings and errors that may explain generator pressure even when
samples appear successful.
10. State checklist before tuning
| State | Read-only question |
|---|---|
| JMeter/JVM | Exact JMeter/Java, heap/GC flags, heap occupancy, GC counts/time, process CPU/threads? |
| Thread/arrival | Threads/loops/timers/rate and expected/achieved samples/RPS? |
| Components | Listeners/assertions/scripts/parsers/payload sizes/keep-alive and connection settings? |
| Variables/properties/data | Any runtime object creation, huge variables/data, per-thread caches? |
| Protocol/session | HttpClient4/keep-alive/TLS/DNS/redirect/retry/session behavior? |
| OS | Handles/FDs, sockets by state, dynamic-port range, process/file limits? |
| Network/DNS | NIC bytes/drops, resolver/cache state, source address/routing? |
| Results/disk | CSV/XML fields, response retention, JTL/log size, free disk/write pressure? |
| Target | Service-wall time/errors/resource state independently stable? |
| Validity | Does achieved work match configured work while injector maintains accepted headroom? |
11. Read-only/non-destructive inspection first
Windows:
# 1) Find the JMeter JVM PID (inspect CommandLine before using it).
Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq "java.exe" -and $_.CommandLine -like "*ApacheJMeter.jar*" } |
Select-Object ProcessId, CommandLine
# 2) JVM / process snapshots. Replace <PID>.
jcmd <PID> VM.version
jcmd <PID> VM.flags
jcmd <PID> GC.heap_info
jstat -gcutil <PID> 1000 12
Get-Process -Id <PID> |
Select-Object Id,CPU,WorkingSet64,PrivateMemorySize64,Handles,Threads
# 3) TCP/socket state owned by the JVM.
Get-NetTCPConnection -OwningProcess <PID> -ErrorAction SilentlyContinue |
Group-Object State | Select-Object Name,Count
# 4) Inspect the OS dynamic TCP port range; DO NOT change it in this lab.
netsh int ipv4 show dynamicport tcp
netsh int ipv6 show dynamicport tcp
# 5) DNS state / lookup path.
Resolve-DnsName localhost
Get-DnsClientCache | Select-Object -First 20
# 6) Network adapter counters.
Get-NetAdapterStatistics |
Select-Object Name,ReceivedBytes,SentBytes,ReceivedDiscardedPackets,OutboundDiscardedPackets
# 7) Disk/result artifact.
Get-Item .\results\<run>\results.jtl |
Select-Object FullName,Length,LastWriteTime
Get-Volume | Select-Object DriveLetter,SizeRemaining,Size
Linux:
# Identify the JMeter JVM.
jps -lv
# JVM heap/GC (replace PID).
jcmd PID VM.version
jcmd PID VM.flags
jcmd PID GC.heap_info
jstat -gcutil PID 1000 12
# Process CPU/memory.
ps -p PID -o pid,pcpu,pmem,rss,vsz,nlwp,etime,cmd
pidstat -p PID 1 10 # if sysstat/pidstat is installed
# File descriptors and sockets.
ls /proc/PID/fd | wc -l
ss -tanp
ulimit -n
cat /proc/sys/fs/file-max
# Dynamic local port range (read-only).
sysctl net.ipv4.ip_local_port_range
# DNS state.
getent hosts localhost
resolvectl statistics # where systemd-resolved is present
# Network/disk state.
ip -s link
df -h .
du -h results/*/results.jtl
jcmd GC.heap_info is a medium-impact inspection
command; use it at sensible checkpoints, not in a tight loop. JDK 17
documents jstat as experimental/unsupported, so retain
the exact JDK build and corroborate with JMeter/OS evidence.
12. Optional generator-only reference
JMeter best practices notes that the built-in JavaTest sampler requires no network access and can help estimate raw JMeter execution throughput on a platform. A short JavaTest run can separate “JMeter/JVM overhead” from HTTP/network cost, but it is not a substitute for sizing the real HTTP plan.
13. DevOps connection
Generator capacity is infrastructure capacity. Treat each injector like a production service: inventory its CPU/RAM/NIC/storage/JVM/OS limits, monitor it, version its config, define headroom gates and scale horizontally only after one engine's usable capacity is measured. Chapter 25's distributed total-load math is only trustworthy when each engine can actually deliver its assigned share.
Knowledge check
Why can a faster server increase JMeter CPU usage?
Threads complete requests sooner and execute more samples/processing per second, so injector work rate rises.
Why is 1 GiB not a universal JMeter heap recommendation?
It is the launcher default; actual requirement depends on plan design, allocation/live set and active workload.
What does a large TIME_WAIT population suggest?
Recent TCP connection churn; if connection reuse should be occurring, it can indicate unnecessary socket/ephemeral-port pressure.
Why use 127.0.0.1 in the mandatory benchmark?
It removes DNS and external network variability so generator/JMeter connection behavior is easier to isolate.
When is a target-capacity claim invalid even if JTL has no failures?
When generator CPU/GC/socket/network/disk constraints reduced or distorted achieved load relative to the intended experiment.
Official references and version notes
- Apache JMeter downloads — current stable release and Java requirement.
- Apache JMeter current changes — Java guidance for the 5.6.x line.
- JMeter Getting Started — load-generator sizing, CLI guidance, default heap/GC launcher settings and JVM environment variables.
- JMeter Best Practices — effective thread count, CLI execution, listener/result minimization, CSV output and generator resource reduction.
- Component Reference — HTTP Request — HttpClient4 default, keep-alive behavior and response-body MD5 option.
- Component Reference — DNS Cache Manager — JVM DNS cache behavior and HttpClient4-only per-thread DNS control.
- JMeter Properties Reference — HttpClient4 retry, connection TTL/validation and result-save properties.
-
JDK 17
jcmd— JVM process/heap inspection. -
JDK 17
jstat— GC/heap utilization statistics; documented as experimental/unsupported.
Version-sensitive statements were rechecked against current
primary documentation on 2026-09-05. The course baseline remains
Apache JMeter 5.6.3 with a Java 17 JDK. JMeter
5.6.3 requires Java 8+; the 5.6.x changes page recommends Java 17
or later. Current JMeter launcher scripts default to a 1 GiB heap
(-Xms1g -Xmx1g plus a 256 MiB metaspace cap) and G1GC
with
-XX:MaxGCPauseMillis=250/-XX:G1ReservePercent=20. Those defaults are a
starting point, not a universal sizing rule. JMeter's HTTP sampler
default is HttpClient4. Its retry count defaults to 0; its
documented connection TTL defaults to 60 seconds. The HTTP Request
Use KeepAlive option is effective with the Apache
HttpComponents implementation and is the only plan change used in
the checkpoint. JMeter's own best-practice guidance says effective
thread capacity depends on hardware, plan design and how fast the
target responds; CLI mode, minimal listeners, CSV and only
required fields reduce generator cost. The mandatory lab never
changes OS ephemeral-port ranges, file-descriptor limits,
firewall/antivirus state or global DNS/JDK security settings.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.