JVM 内存优化与 GC 优化

GC 调优的思路

JVM GC 调优的主要思路是避免 FullGC,同时选择合适的垃圾回收器, 减少 STW 的时间,
确保用户的整体体验。

服务器信息

秒杀接口服务器, 配置为 4 8G

压测服务器,配置为4核8G

压测工具AB

Ab(ApacheBench)测试工具是Apache提供的一款测试工具,具有简单易上手的特点,在测试Web服务时非常实用。
ab一般都是在Linux上用。
安装非常简单,只需要在Linux系统中输入yum-yinstallhttpd-tools命令,就可以了。安装成功后,输入ab命令,可以看到以下信息:

ab工具用来测试postget接口请求非常便捷,可以通过参数指定请求数、并发数、请求参数等
测试get请求接口
ab-c10-n100http://www.test.api.com/test/login?userName=test&password=test
测试post请求接口
ab-n100-c10-p’post.txt’-T’application/x-www-form-urlencoded’ ‘http://test.api.com/test/register’
post.txt为存放post参数的文档,存储格式如usernanme=test&password=test&sex=1
参数的含义:

-n:总请求次数(最小默认为1);
-c:并发次数(最小默认为1且不能大于总请求次数,例如:10个请求,10个并发,实际就是1人请求1次);
-p:post参数文档路径(-p和-T参数要配合使用);
-T:header头内容类型(此处切记是大写英文字母T);

输出中,性能指标参考

Requestspersecond:吞吐率,指某个并发用户数下单位时间内处理的请求数;
Timeperrequest:上面的是用户平均请求等待时间,指处理完成所有请求数所花费的时间/(总请求数/并发用户数);
Timeperrequest:下面的是服务器平均请求处理时间,指处理完成所有请求数所花费的时间/总请求数;
Percentageoftherequestsservedwithinacertaintime:每秒请求时间分布情况,指在整个请求中,每个请求的时间长度的分布情况,例如有50%的请求响应在8ms内,66%的请求响应在10ms内,说明有16%的请求在8ms~10ms之间。

AB压测秒杀接口(调优前)

后台启动秒杀接口

nohup java -jar mall-web-order.jar  –spring.profiles.active=beta  –eureka.instance.ip-address=106.52.62.38&
直到Eureka注册成功

AB压测

对秒杀服务进行压力测试,模拟以下并发用户数下的服务的响应情况:
200个并发用户/20万请求量(总)
压测准备(需要准备seesion信息):
ab-n 200000 -c 200 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’
‘http://94.191.83.120:8186/mall/api/killgoodsSpec/kill’
在压测目录下创建一个post.txt,保存一下信息
killId=25
JSESSIONID=6308426a-60dd-43e3-af0e-73364df5a2e0

GC监控

还有一句话,无监控不调优,所以我们需要监控起来。JVM中我们使用jstat命令监控一下JVM的GC情况。
统计GC的情况。
jstat- gc 7131 5000 50 |awk ‘{print$13,$14,$15,$16,$17}’

堆空间监控

在默认不配置JVM堆内存大小的情况下,JVM根据默认值来配置当前内存大小。我们可以通过以下命令来查看堆内存配置的默认值:
java-XX:+PrintFlagsFinal-version|grepHeapSize

这台机器上启动的JVM默认最大堆内存为2048MB,初始化大小为128MB。

压测结果

1、200个并发用户/20万请求量(第一次)

压测前的GC情况

YGC YGCT FGC FGCT GCT

22    0.273 4     0.551 0.824

使用AB进行压力测试:

ab -n 200000 -c 200 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’ http://dwfapi.goldwind.com.cn:8280/services/rest/authentication/1.0.0/

统计GC情况
jstat- gc 9656 5000 20 |awk ‘{print$13,$14,$15,$16,$17}’

YGC YGCT FGC FGCT GCT

52    1.178 4     0.551 1.729

结果 30次YGC0.9s

2、200个并发用户/20万请求量(第二次)

使用AB进行压力测试:
ab -n 200000 -c 200 -p ‘post.txt’-T’application/x-www-form-urlencoded’ ‘http://106.55.152.41:8186/mall/api/killgoodsSpec/kill’

统计GC情况
jstat -gc 9656 2000 50 |awk ‘{print$13,$14,$15,$16,$17}’

YGC YGCT FGC FGCT GCT

88    2.372 5     1.165 3.537

结果:
36次YGC1.2s
1次FGC0.6s

结果分析

GC频率
过多的YGC会给系统带来压力,STW的时间达到了1.2s
FullGC带来很大的问题,FullGC一般在生产服务器上会放大,一般至少10倍及以上(因为这个项目只是压测秒杀接口,真实服务器上还有其他的接口在访问,也在产生对象,对于FGC的影响更大)

1、调整新生代、老年代的大小

2、选择垃圾回收器

AB压测秒杀接口(CMS+堆空间调整后)

调优方案
堆空间不变2G,新生代1.5G,初始值和最大值一样。采用CMS垃圾回收器

nohup java -jar -XX:+UseConcMarkSweepGC -Xms2000m -Xmx2000m -Xmn1500m
 mall-web-order.jar –spring.profiles.active=beta
 –eureka.instance.ip-address=106.52.62.38&

1、200个并发用户/20万请求量(第一次)

使用AB进行压力测试:
ab -n 200000 -c 200 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’ ‘http://106.52.62.38:8186/mall/api/killgoodsSpec/kill’

           YGC  YGCT  FGC   FGCT   GCT

压测前  7      0.310    4     0.326  0.636

压测后  17    0.880    4     0.326  1.206

结果
10次YGC 0.57s
YGC次数减少,YGC总耗时减少

2、200个并发用户/20万请求量(第二次)

使用AB进行压力测试:
ab -n 200000 -c 200 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’
 ‘http://106.52.62.38:8186/mall/api/killgoodsSpec/kill’
统计GC情况

                   YGC  YGCT  FGC   FGCT   GCT

压测前         7      0.310    4     0.326  0.636

压测一次后  17    0.880    4     0.326  1.206

压测二次后  26    1.527    4     0.326  1.852

结果
9次YGC 0.64s
成功避免FullGC

AB压测秒杀接口(G1+堆空间调整后)

调优方案
nohup java -jar -XX:+UseG1GC -Xms2000m -Xmx2000m mall-web-order.jar –spring.profiles.active=beta –eureka.instance.ip-address=106.55.152.41&
1、200个并发用户/20万请求量(第一次)

统计GC情况

                   YGC  YGCT  FGC   FGCT   GCT

压测前         12     0.320    0     0.000  0.320

压测一次后  22     0.850    0     0.000  0.850

结果
10次YGC0.53s

2、200个并发用户/20万请求量(第二次)

使用AB进行压力测试:
ab -n 200000 -c 200 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’
 ‘http://106.52.62.38:8186/mall/api/killgoodsSpec/kill’

统计GC情况

                   YGC  YGCT  FGC   FGCT   GCT

压测前         12     0.320    0     0.000  0.320

压测一次后  22     0.850    0     0.000  0.850

压测二次后  31     1.671    0     0.000  1.671

结果
9次YGC 0.82s
完全避免FGC,但是YGC对比CMS总体STW要长(一般压测第一次是预热,第二次可以算做标准的结果),这个是因为G1采用化整为零的处理方式,也是需要付出一定的代价的。
(实战验证理论)

结果分析

GC频率
高频的FullGC会给系统带来非常大的性能消耗,虽然MinorGC相对FullGC来说好了许多,但过多的MinorGC仍会给系统带来压力。
内存
这里的内存指的是堆内存大小,堆内存又分为年轻代内存和老年代内存。堆内存不足,会增加MinorGC,影响系统性能。
一般推荐对于追求性能的服务,比如秒杀等服务,内存一定最好给足(-Xms和-Xmx设置成一样大),一来避免拓展空间的FGC,二来避免OOM。
吞吐量
频繁的GC将会引起线程的上下文切换,增加系统的性能开销,从而影响每次处理的线程请求,最终导致系统的吞吐量下降。
所以综合上述,一般来说PS的组合的垃圾回收器,吞吐量最大,但是就是无法避免最大的STW的时间。
延时
JVM的GC持续时间也会影响到每次请求的响应时间。
可以通过堆中分代的大小,减少GC次数,减少GC带来的影响。
不过这个优先点应该是垃圾回收器的选择,选择低延迟的垃圾回收器。
JDK1.8首选G1,堆空间较小使用CMS。临界值一般是6~8G的堆空间。不过CMS本身有内存碎片和浮动垃圾的问题。推荐还是使用G1

推荐策略

1、新生代大小选择

  • 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,新生代收集发生的频率也是最小的.同时,减少到达老年代的对象.
  • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用.
  • 避免设置过小.当新生代设置过小时会导致:1.MinorGC次数更加频繁2.可能导致MinorGC对象直接进入老年代,如果此时老年代满了,会触发FullGC.

2、老年代大小选择


  • 响应时间优先的应用:老年代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在新生代和老年代回收上的时间比例。
  • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而老年代尽存放长期存活对象。

JVM调优分类

调优是一个很大的概念,简单说就是把系统进行优化,但是站在一个系统的角度,能够干的事情太多了,我们一般把JVM调优分成以下三类:
♦ JVM预调优
♦ 优化JVM运行环境(慢、卡顿等)
♦ 解决JVM中的问题(OOM等)
调优中,现象最明显的是OOM,因为有异常抛出,当然它也只是作为调优的一部分。其次是慢和卡顿的问题。这些都在JVM三期中讲过。
最后才是GC调优,这个也就是今天讲课的内容,主要就是减少STW带来的影响,这些测试点和内容大小设置都需要根据不同的项目进行调整,所以这个是一个耐心活。

单击 “编辑” 按钮更改此文本。这是测试文本。