准备一个小案例,提供一个接口,每次调用都会生成1个1M的对象,然后方法返回,对象由垃圾收集器自动回收。
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GCController {
@RequestMapping("/gcTest")
public String gcTest() {
byte[] bytes = new byte[1024 * 1024];
return "success";
}
}
测试意图:使用压测工具模拟并发请求这个接口,并通过调整堆内存分配,看看对吞吐量、请求等待时间等指标的影响。
压测工具:apache bench
服务器环境:linux虚拟机,2g内存,单核处理器。
使用默认参数启动服务,并查看默认堆内存分配情况
1、模拟10个并发请求10万次
ab -c 10 -n 100000 http://127.0.0.1:8080/gcTest
测试结果:
总耗时:38.675 seconds
吞吐量:2585.64 [#/sec]
用户平均请求等待事件:3.868 [ms]
服务器平均请求处理时间:0.387 [ms]
YGC:12566次,耗时:12.336秒,FGC:2次,耗时:0.052,总计耗时:12.387秒
2、模拟100个并发请求10万次
ab -c 100 -n 100000 http://127.0.0.1:8080/gcTest
测试结果:
总耗时:44.511 seconds
吞吐量:2246.62 [#/sec]
用户平均请求等待事件:44.511 [ms]
服务器平均请求处理时间:0.445 [ms]
YGC:7851次,耗时:16.673秒,FGC:3次,耗时:0.116,总计耗时:16.789秒
使用指定堆大小和老年代大小参数启动服务
-Xms1500m -Xmx1500m
1、模拟10个并发请求10万次
ab -c 10 -n 100000 http://127.0.0.1:8080/gcTest
测试结果:
总耗时:37.939 seconds
吞吐量:2635.84 [#/sec]
用户平均请求等待事件:3.794 [ms]
服务器平均请求处理时间:0.379 [ms]
YGC:261次,耗时:0.462秒,FGC:2次,耗时:0.154,总计耗时:0.616秒
2、模拟100个并发请求10万次
ab -c 100 -n 100000 http://127.0.0.1:8080/gcTest
测试结果:
总耗时:36.389 seconds
吞吐量:2748.05 [#/sec]
用户平均请求等待事件:36.389 [ms]
服务器平均请求处理时间:0.364 [ms]
YGC:260次,耗时:0.552秒,FGC:2次,耗时:0.155,总计耗时:0.707秒
使用指定堆大小、老年代、新生代大小参数启动服务
-Xms1500m -Xmx1500m -Xmn1000m
1、模拟10个并发请求10万次
ab -c 10 -n 100000 http://127.0.0.1:8080/gcTest
测试结果:
总耗时:36.166 seconds
吞吐量:2765.06 [#/sec]
用户平均请求等待事件:3.617 [ms]
服务器平均请求处理时间:0.362 [ms]
YGC:130次,耗时:0.254秒,FGC:2次,耗时:0.213,总计耗时:0.467秒
2、模拟100个并发请求10万次
ab -c 100 -n 100000 http://127.0.0.1:8080/gcTest
测试结果:
总耗时:34.479 seconds
吞吐量:2900.33 [#/sec]
用户平均请求等待事件:34.479 [ms]
服务器平均请求处理时间:0.345 [ms]
YGC:130次,耗时:0.479秒,FGC:2次,耗时:0.144,总计耗时:0.623秒。
最终测试结果
总结
本次测试模拟接口在高并发频、高频次调用时,频繁的创建对象、造成新生代空间不足,频繁的触发新生代垃圾回收。
比如秒杀接口的调用,线上注意合理分配新生代大小,默认新生代与老年代比例为1:2,一些特定的场景也可以适当调整占比,以减少新生代垃圾回收的次数,通过jstat观察垃圾回收的情况,当新生代回收次数特别多,老年代特别少,可以适当进行调整。