在实际工作中,经常会需要进行在全链路压测,优化 GC参数,优化 JVM 内存分配。
当知道 1 次 RPC 请求和 Http 请求需要的堆内存大小后,你可以精确地计算:指定的并发量之下,系统需申请多少堆内存。同时结合 JVM 新生代堆大小,就能推算出 1 分钟发生多少次 GC,这个 GC频率是否过于频繁?从而针对性的优化。
我们希望 1 次 Rpc、Http 请求申请堆内存足够少,这样可减少 GC 导致的系统停顿,提高系统性能,单机可以支撑更高的并发量。
1次 Http 请求,申请多少堆内存?
1 次 RPC 请求,申请多少堆内存?
如果不亲自实验,无法得出结论。
-
创建SpringBoot新应用(版本
2.5.4
)。
-
-
JMeter(开源压测工具)新建测试计划。每个线程执行2000 次Http接口调用,共10 个线程,总调用 20000 次。
-
SpringBoot 打印 GC 详细日志,记录GC 前后,新生代申请了多少内存。
Jmeter 调用 20000 次 Http 接口以后,通过手动 GC 的方式触发 GC,通过 GC 详细日志计算压测期间新生代堆内存增长量。(对象基本分配在新生代)
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
-
视频教程:https://doc.iocoder.cn/video/
如下代码声明了一个 Post接口 create;创建了 Get 接口,用于触发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("收到提单请求 cnt{}:{}", count.getAndIncrement(), order);
return "ok";
}
@ResponseBody
@RequestMapping(value = "gc", method = RequestMethod.GET)
public String gc() {
System.gc();
return "ok";
}
}
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
-
项目地址:https://github.com/YunaiV/yudao-cloud
-
视频教程:https://doc.iocoder.cn/video/
新建线程组,选择 10 个线程,每个线程循环 2000次。
由于请求体是 JSON,所以新增请求头 Content-Type
请求中指定 Url 和 请求体
堆内存大小 4G,其中新生代内存 2G。
SurivivorRadio=8
,即每个 Surivivor 占比新生代 1/10。
指定GC日志位置:
-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.jar
由于JVM 启动过程中,需要加载大量对象,所以我们在压测之前先手动 GC,清理一下存量对象。
curl http://localhost:8080/gc
执行 JMeter压测计划,每次执行会调用 20000 次,
GC 以后,新生代 Eden 区已使用内存为 0。GC 前 Eden区大小就是 20000 次 Http 调用所申请的内存总和!
SpringBoot 在处理 Http 请求时,即使请求体相对较小,
平均每次 Http 调用仍会申请约 34 K 的堆内存
。这一点显得尤为突出,因为请求体仅包含 50 个字符,远远未达到 1K 大小。