文章内容
一、性能分析基本流程
1、前提条件
- 测试方案中的指标:性能是否通过的标准之一
- 性能监控中的数据:性能分析的依据
2、性能问题的分类
- 1. 响应慢
- 进程CPU飙高,load高
- load高,CPU低
- 2. 无响应
- 大量失败, CPU低, load低
- 3. TPS上不去
- 响应较快,但TPS较低(CPU高,load低)
- 4. 内存泄漏
- 5. 内存溢出
- 6. GC频繁
3、案例:20路并发下,某接口的业务指标如下,对每个指标进行分析
- 1. 事务失败占比–0%
- a. 本接口未出现失败数, 说明当前所有事务的返回结果 符合预期断言
- b. 如果有失败的情况, 需要结合最大响应时间判断,失败原因是因为超时还是业务处理失败
- c. 对于业务处理的失败,需要到业务日志中去查找,可能是因为并发导致异常
- 2. TPS 31.5次/秒
- a. 1000/平均响应时间*并发数 ~ TPS
- b. 通过这个关系明确负载机, 性能脚本之间是否存在问题
- c. 期望值是: TPS > 并发数
- 3. 平均响应时间 632ms(本公司要求指标是: 该路数并发下90%的响应时间)
- a. 以测试方案中的指标作为参考, 形成基本判断, 再分析响应构成
- b. 平均响应时间 > 方案中期望的时间: 不满足性能要求, 需要分析排查性能问题或者增加机器
- c. 平均响应时间 <= 方案中期望的时间: 满足性能要求, 但仍要分析排查是否有性能问题
- d. 平均响应时间 = 网络传输时间 + 系统处理时间
- e. 系统处理时间依赖业务日志统计: 平均响应时间 – 系统处理时间
- 如果差距很大:
- (1)测试环境网络问题
- (2)带宽限制问题
- 如果差距很小
- (1)分析系统处理时间
- 如果差距很大:
- 4. 被测接口内部逻辑:
1)统计整体耗时和重要RPC耗时
2)耗时逻辑分析
- 外部调用耗时, 如数据库, 后端服务, 外部地址, 需要判断合理性
- 注意返回书籍大小对耗时的影响(测试环境可能出现带宽限制)
- 内部逻辑耗时, 深入到具体的方法调用, 比如序列化和反序列化
3)比如Java处理json数据的三种方法:FastJSON,Gson和Jackson
a)序列化(object ==> json)
b)反序列化(json ==> object)
4)接着从资源指标入手来分析
二、Java应用内存分析以及优化
1、Java内存分配主要包括以下几个区域
- 栈:存放基本类型的数据和对象的引用, 但对象本身不存放在栈中, 而是存放在堆中
- 堆:存放用new产生的数据
- 静态域:存放在对象中用static定义的静态成员
- 常量池:存放常量
- main方法开始执行: int date = 9; data局部变量,基础类型,引用和值都存在栈中
- Test test = new Test(); test为对象引用, 存在栈中,对象(new Test())存在堆中
- test.change(date); i为局部变量, 引用和值存在栈中,当方法change执行完成后, i就会从栈中消失
- BirthDate d1 = new BirthDate(7,7,1970); d1为对象引用, 存在栈中,对象(new BirthDate())存在堆中, 其中d, m, y为局部变量存储在栈中, 且他们的类型为基础类型, 因此他们的数据也存储在栈中, day, month, year为成员变量, 他们存储在堆中(new BirthDate()里面). 当BirthDate构造方法执行完之后, d, m, y将从栈中消失
- main方法执行完之后, date变量, test, d1引用将从栈中消失, new Test(),new BirthDate()将等待垃圾回收
2、Java常见的内存问题表现形式
- OutOfMemory-内存溢出:是指程序在申请内存时, 没有足够的内存空间供其使用
- MemoryLeak-内存泄漏:始终程序在申请内存后, 无法是否已申请的内存空间
1)导致OutOfMemoryError异常的常见原因有以下几种
- 1. 内存中加载的数据量过于庞大, 如: 一次从数据库中取出过多数据
- 2. 集合类中有对对象的引用, 使用完成后未清空, 使得jvm不能回收
- 3. 代码中存在死循环或者循环过程中产生过多重复的对象实体
- 4. 使用第三方软件中的BUG
- 5. 启动参数内存值设定的过小
- (1) 内存泄漏是导致内存溢出的原因之一, 内存泄漏积累起来将导致内存溢出
- (2) 内存泄漏可以通过完善代码来避免, 内存溢出可以通过调整配置来减少发生频率, 但无法彻底避免
3、内存溢出类型
虚拟机栈溢出, 本地方法栈溢出, 方法区溢出, 堆溢出, 运行时常量池溢出
1)异常类型
- java.lang. OutOfMemoryError: Java heap space:堆内存溢出
- 优化: 通过-Xmm(最小值) -Xms(初始值) -Xmx(最大值) 参数手动设置Heap(堆)的大小
- java.lang.OutOfMemoryError: PermGen space:PermGen Space溢出(方法区溢出, 运行时常量池溢出)
- 优化: 通过MaxPermSize参数设置PermGen apsce大小
- java.lang.StackOverflowError:栈溢出(虚拟机栈溢出, 本地方法栈溢出)
- 优化: 通过Xss参数调整
2)堆内存分析
a)收集堆内存
命令:
jmap -dump:format=b,file=heap.dump <pid>
或者
JVisualVM
b)使用工具分析堆内存
- 工具:MAT-elipse插件
点击了”Details”链接之后,除了在上一页看到的描述外,还有Shortest Paths To the Accumulation Point和Accumulated Objects部分,这里说明了从GC root到聚集点的最短路径,以及完整的reference chain。
- Analysis: IBM HeapAnalyzer ( ha456.jar )
- Analysis: jmap –histio:live | more
三、Java应用CPU问题分析优化
1、现象: CPU占用高, 程序响应慢
要了解本身是否为CPU密集型,比如大量的计算等,以及被测服务器的核心数。
2、分析方法
1)保存dump文件
jstack <pid> >> thread.dump
2)找到导致CPU高的线程
top -H -p <pid>
3)Pid 十进制转十六进制
4)找到对应的线程,打开 thread.dump文件
查找:按十六进制值
找到对应的线程,把相关的方法找出来,可以精确到代码的行
5)Jvisualvm CPU 抽样器
四、Java应用线程问题分析优化
1、保存堆栈信息
Jstack >> thead.dump
2、分析线程状态
cat thead.dump | grep ‘java.lang.Thread.State’ | awk ‘{print $2$3$4$5}’ | sort | uniq -c
3、线程状态
- RUNNABLE:线程正在执行中,占用了资源,比如处理某个请求/进行计算/文件操作等
- BLOCKED/Waiting to lock
- 线程处于阻塞状态,等待某种资源(可理解为等待资源超时的线程);
- “waiting to lock ”,即等待给xxx上锁,grep stack文件找locked 查找获得锁的线程;
- “waiting for monitor entry” 线程通过synchronized(obj){……}申请进入了临界区,但该obj对应的monitor被其他线程拥有,从而处于等待。
- WAITING/TIMED_WAITING{定时}
- Deadlock:死锁,资源相互占用
- IO阻塞(程序表现为响应慢,Load高):线程状态为“in Object.wait()”,说明正在等待线程池可用资源,由于线程池满导致新的IO请求处于排队等待状态,且发生在:at com.iflytek.diange.data.provider.sendsong.impl.SendSongImpl.getSendSongInfosByUserId(SendSongImpl.java:92)行
- 死锁(程序表现为无响应):线程状态为“waiting to lock”: 两个线程各持有一个锁,又在等待另一个锁,故造成死锁,且发生在DeadLockTest.java:39行