项目JVM优化之GC优化实战

一、项目背景

一个高并发系统中的抢购接口,高峰时 5W 的并发请求,且每次请求会产生 20KB 对象(包括订单、用户、优惠券等对象数据)。
我们可以通过一个并发创建一个 1MB 对象的接口来模拟万级并发请求产生大量对象的场景,具体代码如下:
在这里插入图片描述

二、准备工作

1.linux机器
我本机起一台 Linux 虚拟机,分配的内存为 2G,处理器数量为 2 个,如下
在这里插入图片描述

2.apache AB压测工具
安装和使用方法,此处不做过多介绍,参考传送门

3.JVM参数
查看JVM进程号

jps

监控JVM中minorGC和fullGC的时间、次数等信息

jstat -gc 进程号 5000 20 | awk '{print $13,$14,$15,$16,$17}'

查看JVM中堆内存使用情况

jmap -heap 进程号

三、压测步骤

首先启动项目
在这里插入图片描述
通过jmap -heap 进程号查看堆默认分配情况为:

最大堆内存为 480m、初始化大小为 32m。
Eden区:103m
From区、To区:3~4m
OLd区:18m

1.10 个并发用户/10万请求量(总)
使用ab命令压测

ab -c 10 -n 100000 http://127.0.0.1:8080/jvm/heap

执行结束后查看结果
在这里插入图片描述

查看gc情况

jstat -gc 9656 5000 20 | awk '{print $13,$14,$15,$16,$17 }'

在这里插入图片描述
通过两图结果可知

  • 用户的吞吐量大于在 1426/每秒左右
  • JVM 服务器平均请求处理时间 0.7ms 左右
  • JVM 服务器发生了 2700 多次 YGC,耗时 15 秒 ;还有 45 次 FGC,2.3 秒左右。加在一起 GC 耗时 17 秒

2.100个并发用户/10万请求量(总)

步骤同上,下面直接说现象

  • 用户的吞吐量大于在 1262/每秒左右
  • JVM 服务器平均请求处理时间 0.8ms 左右
  • JVM 服务器发生了 2700 多次 YGC,耗时 30 秒 ;还有 56 次 FGC,3 秒左右。加在一起 GC 耗时 33 秒

3.1000个并发用户/10万请求量(总)

步骤同上,下面直接说现象

  • 用户的吞吐量大于在 1145/每秒左右
  • JVM 服务器平均请求处理时间 0.8ms 左右
  • JVM 服务器发生了 2700 多次 YGC,耗时 38 秒 ;还有 47 次 FGC,3 秒左右。加在一起 GC 耗时 42 秒

四、结果分析

  • 堆内存

默认不配置堆内存大小和比例的情况下,JVM将采用默认的配置,而如果堆越小,minorGC次数就越多

  • 吞吐量
    GC耗时越久,吞吐量越低; fullGC越频繁,吞吐量越低

五、方案调整

通过上述结果,我们发现存在大量minorGC,虽然fullGC次数相比minorGC次数较少,但是也很影响性能。于是,我们调整堆大小,将堆内存设置大一点

方案一:调大堆空间,但是不设置SurvivorRatio

启动命令改为

java -jar -Xms1500m -Xmx1500m jvm-1.0-SNAPSHOT.jar

这里说句题外话。一般我们设置堆大小时,都是将最大堆和最小堆参数设置成一样,这样可以提前规划好固定内存,避免动态扩容,防止jvm运行过程中出现堆内存不够的情况。

通过jmap -heap 进程号查看堆默认分配情况为:

最大堆内存为 1.5G,初始化大小为 1.5G
Eden区:375m
From、To区:62m
OLd区:1000m

用与之前同样的压测方式,结果如下
1.10 个并发用户/10万请求量(总)

  • 用户的吞吐量大于在 1205/每秒左右
  • JVM 服务器平均请求处理时间 0.83ms 左右
  • JVM 服务器发生了 800 次 YGC,耗时 33 秒 ,还有 1 次 FGC,1 秒左右,加在一起 GC 耗时 34 秒

2.100个并发用户/10万请求量(总)

  • 用户的吞吐量大于在 989/每秒左右
  • JVM 服务器平均请求处理时间 1.01ms 左右
  • JVM 服务器发生了 800 次 YGC,耗时 46 秒 ,还有 8 次 FGC,6 秒左右,加在一起 GC 耗时 52 秒

3.1000个并发用户/10万请求量(总)

  • 用户的吞吐量大于在 749/每秒左右
  • JVM 服务器平均请求处理时间 1.3ms 左右
  • JVM 服务器发生了 800 次 YGC,耗时 66 秒 ,还有 8 次 FGC,9 秒左右,加在一起 GC 耗时 75 秒
方案二:调大堆空间,同时设置SurvivorRatio

启动命令改为

java -jar -Xms1500m -Xmx1500m -Xmn1000m -XX:SurvivorRatio=8 jvm-1.0-SNAPSHOT.jar

通过jmap -heap 进程号查看堆默认分配情况为:

最大堆内存为 1.5G,初始化大小为 1.5G
Eden 区 800m
From、To 100M
OLd =500M
用与之前同样的压测方式,结果如下

1.10 个并发用户/10万请求量(总)

  • 用户的吞吐量大于在 1780/每秒左右
  • JVM 服务器平均请求处理时间 0.56ms 左右
  • JVM 服务器发生了 400 次 YGC,耗时 5.8 秒 ,还有 2 次 FGC,0.1 秒左右,加在一起 GC 耗时 6 秒

2.100个并发用户/10万请求量(总)

  • 用户的吞吐量大于在 1927/每秒左右
  • JVM 服务器平均请求处理时间 0.51ms 左右
  • JVM 服务器发生了 400 多次 YGC,耗时 11 秒 ,没有 FGC,加在一起 GC 耗时 11 秒

3.1000个并发用户/10万请求量(总)

  • 用户的吞吐量大于在 1657/每秒左右
  • JVM 服务器平均请求处理时间 0.6ms 左右
  • JVM 服务器发生了 400 多次 YGC,耗时 14 秒 ,还 1 次 FGC,3 秒左右,加在一起 GC 耗时 17 秒

六、结果总结和分析

在方案一中,虽然调大了堆空间,但是吞吐量和GC耗时的结果相比没有调大时,反而还差了点;
方案二不仅调大了堆空间,还设置了eden和survivor的比例,在所有结果无疑是最好的。
为什么会出现这个情况呢?不管是方案一还是方案二,可以确定的是业务场景里每个对象存活对象时间都是固定的。
而在方案一中,堆空间是增大了,但eden区域并没有很大,因此eden区域还是很快被填满,并开始minorGC。minorGC时大量对象可能还是存活的,因此GC时间为:扫描整个堆空间+复制存活对象。复制存活对象会采用复制算法,是一个很耗时的操作;同时,由于堆空间相比没有任何调整前还大,因此扫描整个堆空间的时间也更长,导致方案一的GC耗时和吞吐量也更低。

在方案二中,eden区域很大,因此出现minorGC的频率相比方案一更低。同时开始minorGC时,大量对象可能都已过了存活时间,因此GC时间为:只扫描整个堆空间,而没有复制存活对象的时间。虽然扫描整个堆空间的时间也更长,但是相比复制的成本,可以忽略。

所以方案二由于方案一。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值