JVM调优实践

对于一个系统要部署上线时,则一定会对JVM进行调整,很少会不经过任何调整直接上线。否则很容易出现线上系统频繁FullGC造成系统卡顿、CPU使用频率过高、系统无反应等问题。

服务器性能指标

​ 对于一个应用来说通常重点关注的性能指标主要是吞吐量、响应时间、QPS、TPS等、并发用户数等。而这些性能指标又依赖于系统服务器的资源,如:CPU、内存、磁盘IO、网络IO等。对于这些指标数据的收集,通常可以根据Java本身的工具或指令进行查询,详情参照第一天。

1)CPU:

CPU资源一般可以使用vmstat来采样(例如每秒采样一次: vmstat 1)查看CPU上下文切换。如下:

$ vmstat 1 

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 284628 234036 859100 0 0 1 15 0 1 3 1 96 0 0
0 0 0 284536 234036 859204 0 0 0 0 4952 9461 4 2 95 0 0
0 0 0 284536 234036 859208 0 0 0 200 5081 9768 3 3 94 1 0
0 0 0 284568 234036 859212 0 0 0 16 5126 9757 3 2 96 0 0
0 0 0 284900 234036 859236 0 0 0 0 5431 10230 4 3 94 0 0
1 0 0 285608 234036 859256 0 0 0 0 5325 10005 6 2 92 0 0
0 0 0 285452 234036 859256 0 0 0 0 5037 9653 3 1 96 0 0
0 0 0 285484 234036 859264 0 0 0 60 5068 9599 3 1 96 0 0
0 0 0 285452 234036 859264 0 0 0 36 5163 9825 4 2 94 0 0
  • us:用户占用CPU的百分比
  • sy:系统(内核和中断)占用CPU的百分比
  • id:CPU空闲的百分比
  • in: 系统中断数
  • cs: 每秒上下文切换次数
  • r: 可运行进程数,包括正在运行(Running)和已就绪等待运行(Waiting)的。在负载测试中,其可接受上限通常不超过CPU核数的2倍。

CPU使用率通常用us + sy来计算,一般大于80%说明,CPU资源出现瓶颈。

2)内存

根据上述信息,其中已经输出了内存的信息

  • free: 系统可用内存,对于稳定运行的系统,free可接受的范围通常应该大于物理内存的20%。
  • so/si : 每秒从内存写入到SWAP的数据大小/每秒从SWAP读取到内存的数据大小。如果出现频繁的swap交换,会影响系统性能,需要一起注意。
  • swpd:系统当前的swap空间占用。可以和so/si 综合分析。如果swpd为0 ,内存资源没有成为瓶颈。

3)磁盘

对于磁盘,首要关注使用率,IOPS和数据吞吐量,在Linux服务区,可以使用iostat来获取这些数据。

$ iostat -dxk 1
Linux 4.4.0-63-generic _x86_64
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 2.69 0.07 2.21 1.28 28.96 26.53 0.00 1.85 1.19 1.87 0.31 0.07

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 2.00 0.00 12.00 0.00 12.00 0.02 10.00 10.00 0.00 10.00 2.00

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 29.00 0.00 2.00 0.00 124.00 124.00 0.00 2.00 0.00 2.00 2.00 0.40
  • %util: 衡量device使用率的指标。处理I/O请求的时间与统计时间的百分比.大于60%的话,会影响系统性能。
  • r/s, w/s :每秒处理,读、写的请求数量。
  • rkB/s,wkB/s :每秒读/写的数据大小。

JVM参数调优

​ 对于JVM调优,主要就是调整年轻代、年老大、元空间的内存空间大小及使用的垃圾回收器类型。

1)设置堆的初始大小和最大大小,为了防止垃圾收集器在初始大小、最大大小之间收缩堆而产生额外的时间,通常把最大、初始大小设置为相同的值。

-Xms:设置堆的初始化大小

-Xmx:设置堆的最大大小

2) 设置年轻代中Eden区和两个Survivor区的大小比例。该值如果不设置,则默认比例为8:1:1。Java官方通过增大Eden区的大小,来减少YGC发生的次数,但有时我们发现,虽然次数减少了,但Eden区满

的时候,由于占用的空间较大,导致释放缓慢,此时STW的时间较长,因此需要按照程序情况去调优。

-XX:SurvivorRatio

3)年轻代和老年代默认比例为1:2。可以通过调整二者空间大小比率来设置两者的大小。

-XX:newSize   设置年轻代的初始大小
-XX:MaxNewSize 设置年轻代的最大大小, 初始大小和最大大小两个值通常相同

4)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,但一般256K就够用。通常减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

-Xss   对每个线程stack大小的调整,-Xss128k

5)一般一天超过一次FullGC就是有问题,首先通过工具查看是否出现内存泄露,如果出现内存泄露则调整代码,没有的话则调整JVM参数。

6)系统CPU持续飙高的话,首先先排查代码问题,如果代码没问题,则咨询运维或者云服务器供应商,通常服务器重启或者服务器迁移即可解决。

7)如果数据查询性能很低下的话,如果系统并发量并没有多少,则应更加关注数据库的相关问题。

8)如果服务器配置还不错,JDK8开始尽量使用G1或者新生代和老年代组合使用并行垃圾回收器。