Java 性能调优 (JVM CPU IO Memery)

  • 寻找性能瓶颈
  • CPU消耗分析(以下基于linux操作系统)
  • 文件IO消耗分析(以下基于linux操作系统)
  • 网络IO消耗分析
  • 内存消耗分析
  • 程序执行慢的原因分析
  • 调优
  • 代大小调优


随着系统访问量不断攀升,系统响应通常也会随之变慢;开发的新需求或者应用性能上无法满足需求。进而就需要对系统进行性能调优。调优是一个复杂的过程,包括硬件、操作系统、运行应用环境及应用本身。
简化为以下几个步骤:


Created with Raphaël 2.2.0 衡量系统现状 设定调优目标 断定性能瓶颈 性能是否满足需求? 性能调优 yes no


  • 衡量系统现状,包括目前系统请求数量、响应时间、资源消耗等信息。如,系统60%响应时间为1秒。
  • 通常有了系统现状,就可以设定性能优化目标。如,提升响应速度为500ms。
  • 设定目标后,需要寻找性能瓶颈所在,根据需求场景进行优化代码、配置等。
  • 优化后继续衡量系统状况,直到满足性能需求为止。

寻找性能瓶颈

一般性能瓶颈表象主要是资源消耗过多、外部服务(第三方服务和基础服务)性能不足;或者资源消耗不多,但响应速度上不去。

  1. 资源消耗主要是CPU、文件IO、网络IO、内存方面。通常机器资源有限,某一方面消耗过多,都会引起系统性能瓶颈发生。
  2. 外部服务,第三方服务响应慢,数据库慢查询等,多数也是资源消耗过多导致的。
  3. 资源消耗不多,响应速度上不去,主要是因为程序代码运行的效率不够高,程序结构不合理或资源利用不充分等导致的。

对于java 应用而言,寻找性能瓶颈主要分析资源消耗和利用率。结合一些工具查找程序中造成资源消耗过多的原因。

CPU消耗分析(以下基于linux操作系统)

cpu主要用于中断、内核以及用户进程的任务处理,优先级为:中断>内核>用户进程。

  • 上下文切换,每个cpu同一时间只能执行一个线程,当到达执行时间,线程中存在IO阻塞或者更高优先级线程执行时,将进行上下文切换,切换前要存储当前线程执行状态,并恢复要执行的线程的状态。对于java应用,文件IO操作、网络IO操作、锁等待或线程sleep时,线程将会进入阻塞或者休眠状态,从而进行上线文切换,上下文切换过于频繁会造成内核使用cpu过多,使得应用响应变慢。
  • 运行队列,每个线程都维护一个可运行任务队列,当运行任务队列堆积越多,说明存在耗时线程存在。
  • 利用率,CPU在用户进程、内核、中断处理、IO等待、空闲5个部分使用百分比。
  1. 通过top或者pidstat命令能查看CPU消耗情况。
    使用top 而后shift+h
$ top
top - 15:26:54 up 539 days,  4:57,  7 users,  load average: 0.01, 0.02, 0.05
Tasks: 269 total,   1 running, 268 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.2 us,  0.2 sy,  0.0 ni, 99.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8004768 total,  3940084 free,  1407824 used,  2656860 buff/cache
KiB Swap:  1048572 total,  1048572 free,        0 used.  5891008 avail Mem

us:用户进程处理所占百分比。
sy:内核线程处理所占百分比。
ni: 优先级任务所占百分比。
id:CPU空闲所占百分比。
wa:执行过程中IO等待所占百分比。
hi:硬件中断所占百分比。
si:软件中断所占百分比。

  1. pidstat 1 2 (安装 yum -y install sysstat) 1秒输出目前活动进程CPU消耗情况,共输出2次。
$ pidstat 1 2
03:25:11 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
03:25:12 PM  1000     22379    0.98    0.00    0.00    0.98     3  pidstat
03:25:12 PM  1000     27929    0.98    0.00    0.00    0.98     1  java

通过pidstat -p [PID] -t 1 2 查看某进程中线程的CPU消耗。

$ pidstat -p 59540 -t 1 2
Average:      UID      TGID       TID    %usr %system  %guest    %CPU   CPU  Command
Average:     1000     59540         -    1.00    0.50    0.00    1.50     -  java
Average:     1000         -     59540    0.00    0.00    0.00    0.00     -  |__java
Average:     1000         -     59541    0.00    0.00    0.00    0.00     -  |__java
Average:     1000         -     59542    0.00    0.00    0.00    0.00     -  |__java
Average:     1000         -     59543    0.00    0.00    0.00    0.00     -  |__java
Average:     1000         -     59544    0.00    0.00    0.00    0.00     -  |__java

说明:TID即为线程ID(10进制),转化为16进制。通过jstack即可定位到当前线程。jstack 59540 | grep -A 10 [TID(16进制)]
除了top、pidstat外,还有vmstat、sar来查看CPU消耗。

CPU消耗严重时,主要体现在us、sy、wa或者hi的值变高。hi过高主要为硬件中断,如网卡接收数据频繁的状况。对于java应用主要体现在us、sy两个值上。通过kill -3 [javapid] dump出java线程信息。结合jstack -l 了解线程执行状态的变化。

  • us过高代表应用消耗了大部分CPU。如程序无阻塞的循环、大计算或者正则匹配,或者频繁GC
  • sy过高代表系统花费更多在时间在上下文切换上。如线程比较多,且线程多数处于不断阻塞(锁、io等待)和执行状态的变化过程中。

文件IO消耗分析(以下基于linux操作系统)

操作文件时,将数据放入文件缓存区,直到内存不足或者系统释放内存给用户进程使用。cached用作提升文件IO速度。

  • pidstat
$ pidstat -d -p 59540 -t 1 2
Average:      UID      TGID       TID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
Average:     1000     59540         -      0.00      0.00      0.00  java
Average:     1000         -     59540      0.00      0.00      0.00  |__java
Average:     1000         -     59541      0.00      0.00      0.00  |__java

kB_rd/s 每秒读取KB数
kB_wr/s 每秒写入的KB数

  • iostat
$ iostat 查看历史各设备的IO历史情况

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.19    0.00    0.13    0.00    0.00   99.68

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sda               1.02         0.02        16.95    1075728  789481803
sdb               0.02         0.00         2.61      10977  121461262
$ iostat -x xvda 3 5
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.19    0.00    0.13    0.00    0.00   99.68

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
  • await 平均每次io等待的时间(毫秒)
  • avgqu-sz 等待请求的队列的平均长度
  • svctm 平均设备执行IO操作的时间
  • util 1秒之中有百分多少用于IO操作

java应用造成文件IO消耗多线程大量内容写入(如频繁的日志写入),磁盘硬件本身处理速度,文件系统慢,或者操作大文件。

网络IO消耗分析

网络IO的消耗是非常值得关注的,尤其注意网卡中断是不是均衡的分配到各CPU。

## 通过执行查看分配情况
$ cat /proc/inerrupts

sar来分析网络IO消耗情况

## 可自行执行,查看结果
$ sar -n ALL 1 2

由于没办法分析具体线程网络IO消耗情况。对于网络IO高时,需要通过dump,分析大量网络IO的线程。由于网络IO也消耗JVM内存,因此一般不会java不会出现网络IO相关问题。

内存消耗分析

分析JVM内存情况可用工具,jmap, jstat, mat, visualvm等方法,JVM消耗内存过多时,会频繁触发GC操作,同时CPU消耗增加,应用执行效率下降,进而会造成OOM,导致java进程退出。

  • vmstat
## 单位KB
$ vmstat
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 3939832     32 2657292    0    0     0     5    0    0  0  0 100  0  0

swpd 虚拟内存已使用部分
free 空闲物理内存
buff 用于缓冲的内存
cache 缓存内存
si 每秒从磁盘读至内存的数据量
so 每秒从内存写入磁盘的数据量

如果swpd过高通常物理内存不够用了,可以观察si so值,磁盘-内存频繁数据交互严重影响系统性能。
对于java应用来说,可能原因JVM设置过大、创建过多线程或者(direct bytebuffer)直接内存.

  • pidstat
    使用如下命令,可以查看该进程所占用物理内存和虚拟内存大小
# pidstat -r -p [PID] [interval] [times]
$ pidstat -r -p 59540 1 2
04:11:28 PM   UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
04:11:29 PM  1000     59540      4.00      0.00 5954636 775168   9.68  java
04:11:30 PM  1000     59540      0.00      0.00 5954636 775168   9.68  java
Average:     1000     59540      2.00      0.00 5954636 775168   9.68  java
  • jstat 分析JVM head使用情况
  • jstat -gcnewcapacity [pid] 新生代内存统计
  • jstat -gc [pid] 垃圾回收统计
  • jstat -gccapacity [pid] 堆内存统计
  • jstat -gcnew [pid] 新生代垃圾回收统计
  • jstat -gcold [pid] 老年代垃圾回收统计
  • jstat -gcoldcapacity [pid] 老年代内存统计
  • jstat -gcmetacapacity [pid] 元数据空间统计

通过top、pidstat结合jstat,用于观察系统内存消耗情况,从而判定问题所在。

程序执行慢的原因分析

有些情况资源消耗不多,但程序执行慢,多数原因如下。

  1. 锁竞争激烈,如数据连接池设置过低,导致竞争。
  2. 未充分使用硬件资源,如CPU多核,程序中都是单线程串行的操作。
  3. 数据量增长,数据库表数据增加,导致读写变慢。
  4. 依赖三方服务,服务响应慢。

调优

代大小调优

  1. 避免新生代大小设置过小, 会引起频繁minor GC, 导致对象直接接入老年代。
  2. 避免新生代大小设置过大,会引起频繁FULL GC,导致minor GC耗时过长.
  3. 避免Survivor区过大或过小
  4. 合理设置新生代存活周期

具体调优方案,要根据实际场景,结合工具进行分析,最终确认合理取值,得到调优的目的。

以上为个人总结,如有问题欢迎指正!