Java 性能调优 (JVM CPU IO Memery)

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

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. 合理设置新生代存活周期

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值