Java 性能调优 (JVM CPU IO Memery)
- 寻找性能瓶颈
- CPU消耗分析(以下基于linux操作系统)
- 文件IO消耗分析(以下基于linux操作系统)
- 网络IO消耗分析
- 内存消耗分析
- 程序执行慢的原因分析
- 调优
- 代大小调优
随着系统访问量不断攀升,系统响应通常也会随之变慢;开发的新需求或者应用性能上无法满足需求。进而就需要对系统进行性能调优。调优是一个复杂的过程,包括硬件、操作系统、运行应用环境及应用本身。
简化为以下几个步骤:
Created with Raphaël 2.2.0 衡量系统现状 设定调优目标 断定性能瓶颈 性能是否满足需求? 性能调优 yes no
- 衡量系统现状,包括目前系统请求数量、响应时间、资源消耗等信息。如,系统60%响应时间为1秒。
- 通常有了系统现状,就可以设定性能优化目标。如,提升响应速度为500ms。
- 设定目标后,需要寻找性能瓶颈所在,根据需求场景进行优化代码、配置等。
- 优化后继续衡量系统状况,直到满足性能需求为止。
寻找性能瓶颈
一般性能瓶颈表象主要是资源消耗过多、外部服务(第三方服务和基础服务)性能不足;或者资源消耗不多,但响应速度上不去。
- 资源消耗主要是CPU、文件IO、网络IO、内存方面。通常机器资源有限,某一方面消耗过多,都会引起系统性能瓶颈发生。
- 外部服务,第三方服务响应慢,数据库慢查询等,多数也是资源消耗过多导致的。
- 资源消耗不多,响应速度上不去,主要是因为程序代码运行的效率不够高,程序结构不合理或资源利用不充分等导致的。
对于java 应用而言,寻找性能瓶颈主要分析资源消耗和利用率。结合一些工具查找程序中造成资源消耗过多的原因。
CPU消耗分析(以下基于linux操作系统)
cpu主要用于中断、内核以及用户进程的任务处理,优先级为:中断>内核>用户进程。
- 上下文切换,每个cpu同一时间只能执行一个线程,当到达执行时间,线程中存在IO阻塞或者更高优先级线程执行时,将进行上下文切换,切换前要存储当前线程执行状态,并恢复要执行的线程的状态。对于java应用,文件IO操作、网络IO操作、锁等待或线程sleep时,线程将会进入阻塞或者休眠状态,从而进行上线文切换,上下文切换过于频繁会造成内核使用cpu过多,使得应用响应变慢。
- 运行队列,每个线程都维护一个可运行任务队列,当运行任务队列堆积越多,说明存在耗时线程存在。
- 利用率,CPU在用户进程、内核、中断处理、IO等待、空闲5个部分使用百分比。
- 通过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:软件中断所占百分比。
- 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,用于观察系统内存消耗情况,从而判定问题所在。
程序执行慢的原因分析
有些情况资源消耗不多,但程序执行慢,多数原因如下。
- 锁竞争激烈,如数据连接池设置过低,导致竞争。
- 未充分使用硬件资源,如CPU多核,程序中都是单线程串行的操作。
- 数据量增长,数据库表数据增加,导致读写变慢。
- 依赖三方服务,服务响应慢。
调优
代大小调优
- 避免新生代大小设置过小, 会引起频繁minor GC, 导致对象直接接入老年代。
- 避免新生代大小设置过大,会引起频繁FULL GC,导致minor GC耗时过长.
- 避免Survivor区过大或过小
- 合理设置新生代存活周期
具体调优方案,要根据实际场景,结合工具进行分析,最终确认合理取值,得到调优的目的。
以上为个人总结,如有问题欢迎指正!