Measuring Per‑Request Heap Memory Usage in SpringBoot with JMeter and GC Logs
This article demonstrates how to quantify the heap memory allocated by individual HTTP and RPC calls in a SpringBoot application by creating a test service, configuring JMeter for load, capturing detailed GC logs, and analyzing the results to guide JVM and logging optimizations.
In real‑world backend work, full‑link load testing, GC tuning, and JVM memory allocation optimization often require knowing how much heap memory each RPC or HTTP request consumes.
When the heap memory required for a single RPC or HTTP request is known, you can precisely calculate the total heap needed for a given concurrency level and estimate the number of young‑generation GCs per minute, allowing targeted performance improvements.
1. Experiment Idea
We aim to keep the heap allocation per RPC/HTTP request as low as possible to reduce GC pauses and increase system throughput, enabling a single machine to handle higher concurrency.
Key Steps
Create a new SpringBoot application (version 2.5.4 ).
Add a POST endpoint for JMeter to invoke.
Configure JMeter: 10 threads, each performing 2,000 HTTP calls (total 20,000 calls).
Enable detailed GC logging in SpringBoot to record memory before and after each GC.
2. Declaring HTTP Endpoints in SpringBoot
The following code defines a POST endpoint /create and a GET endpoint /gc that triggers a manual GC.
@Slf4j
@RestController
public class TestController {
private AtomicLong count = new AtomicLong(0);
@ResponseBody
@RequestMapping(value = "create", method = RequestMethod.POST)
public String create(@RequestBody Order order) {
//log.warn("Received order cnt{}:{}", count.getAndIncrement(), order);
return "ok";
}
@ResponseBody
@RequestMapping(value = "gc", method = RequestMethod.GET)
public String gc() {
System.gc();
return "ok";
}
}3. Building the JMeter Test Plan
3.1 Thread Group
Create a thread group with 10 threads, each looping 2,000 times.
3.2 HTTP Defaults
3.3 Request Header
Since the request body is JSON, add a Content-Type: application/json header.
3.4 HTTP Request
Specify the target URL and request payload.
4. Experiment Execution
4.1 Start SpringBoot
Run the application with a 4 GB heap (2 GB young generation) and set SurvivorRatio=8 (each survivor occupies 1/10 of the young generation).
Specify the GC log location:
-Xloggc:/Users/testUser/log/gc.log java -server
-Xmx4g -Xms4g -XX:SurvivorRatio=8 -Xmn2g
-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g -XX:MaxDirectMemorySize=1g
-XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCCause -XX:+PrintGCDetails
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution
-XX:+UnlockDiagnosticVMOptions -XX:ParGCCardsPerStrideChunk=32768
-XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:ParallelCMSThreads=6 -XX:+CMSClassUnloadingEnabled
-XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelInitialMarkEnabled
-XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+PrintHeapAtGC
-XX:CMSFullGCsBeforeCompaction=1 -XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintReferenceGC
-XX:+ParallelRefProcEnabled -XX:ReservedCodeCacheSize=256M
-Xloggc:/Users/testUser/log/gc.log
-jar target/activiti-0.0.1-SNAPSHOT.jar4.2 Manual GC Before Load
Execute curl http://localhost:8080/gc to trigger a manual GC and clear existing objects before the stress test.
4.3 Run JMeter Load Test
Execute the JMeter plan, which calls the /create endpoint 20,000 times.
4.4 GC Log Analysis
After the test, the Eden space of the young generation is empty, meaning the memory used before GC equals the total memory allocated by the 20,000 HTTP calls.
5. Results
Even with a very small request body (≈50 bytes), each HTTP call in SpringBoot allocates about 34 KB of heap memory, mainly due to internal object creation.
When the detail field is expanded to 1,200 characters, the per‑request memory rises to ~36 KB, confirming that payload size contributes only a small portion of the total allocation.
{"userId": 32898493, "productId": 39043, "detail": ""}Adding a log statement ( log.warn(...) ) increases the average memory per request to ~56 KB, showing that log size can significantly affect memory consumption and GC frequency.
Removing the detail field drops the usage back to ~35.7 KB, reinforcing the impact of logging on heap usage.
6. Real‑World Data
In production, a single RPC request can consume between 0.5 MB and 1 MB of heap due to complex business logic, multiple downstream calls, database accesses, caching, and extensive logging.
With a 5 GB young‑generation Eden space, the system still performs 1–2 young GCs per minute. Rough calculations show that at 500 requests per second, the JVM would need to allocate roughly 15 GB of memory per minute, requiring at least three young GCs to keep up.
Author: 五阳 Source: juejin.cn/post/7416696537684836378
Top Architecture Tech Stack
Sharing Java and Python tech insights, with occasional practical development tool tips.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.