JAVA应用常见性能问题分析与优化

一、性能分析基本流程

1、前提条件

  • 测试方案中的指标:性能是否通过的标准之一
  • 性能监控中的数据:性能分析的依据

2、性能问题的分类

  • 1. 响应慢
    • 进程CPU飙高,load高
    • load高,CPU低
  • 2. 无响应
    • 大量失败, CPU低, load低
  • 3. TPS上不去
    • 响应较快,但TPS较低(CPU高,load低)
  • 4. 内存泄漏
  • 5. 内存溢出
  • 6. GC频繁

3、案例:20路并发下,某接口的业务指标如下,对每个指标进行分析

JAVA应用常见性能问题分析与优化插图
  • 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. 被测接口内部逻辑:
JAVA应用常见性能问题分析与优化插图2

1)统计整体耗时和重要RPC耗时

JAVA应用常见性能问题分析与优化插图4

2)耗时逻辑分析

JAVA应用常见性能问题分析与优化插图6
JAVA应用常见性能问题分析与优化插图8
  • 外部调用耗时, 如数据库, 后端服务, 外部地址, 需要判断合理性
  • 注意返回书籍大小对耗时的影响(测试环境可能出现带宽限制)
  • 内部逻辑耗时, 深入到具体的方法调用, 比如序列化和反序列化

3)比如Java处理json数据的三种方法:FastJSON,Gson和Jackson

a)序列化(object ==> json)
JAVA应用常见性能问题分析与优化插图10
b)反序列化(json ==> object)
JAVA应用常见性能问题分析与优化插图12

4)接着从资源指标入手来分析

二、Java应用内存分析以及优化

1、Java内存分配主要包括以下几个区域

  • :存放基本类型的数据和对象的引用, 但对象本身不存放在栈中, 而是存放在堆中
  • :存放用new产生的数据
  • 静态域:存放在对象中用static定义的静态成员
  • 常量池:存放常量
JAVA应用常见性能问题分析与优化插图14
  1. main方法开始执行: int date = 9; data局部变量,基础类型,引用和值都存在栈中
  2. Test test = new Test(); test为对象引用, 存在栈中,对象(new Test())存在堆中
  3. test.change(date); i为局部变量, 引用和值存在栈中,当方法change执行完成后, i就会从栈中消失
  4. BirthDate d1 = new BirthDate(7,7,1970); d1为对象引用, 存在栈中,对象(new BirthDate())存在堆中, 其中d, m, y为局部变量存储在栈中, 且他们的类型为基础类型, 因此他们的数据也存储在栈中, day, month, year为成员变量, 他们存储在堆中(new BirthDate()里面). 当BirthDate构造方法执行完之后, d, m, y将从栈中消失
  5. 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
JAVA应用常见性能问题分析与优化插图16
b)使用工具分析堆内存
  • 工具:MAT-elipse插件
JAVA应用常见性能问题分析与优化插图18

点击了”Details”链接之后,除了在上一页看到的描述外,还有Shortest Paths To the Accumulation Point和Accumulated Objects部分,这里说明了从GC root到聚集点的最短路径,以及完整的reference chain。

  • Analysis: IBM HeapAnalyzer ( ha456.jar )
JAVA应用常见性能问题分析与优化插图20
  • Analysis: jmap –histio:live | more
JAVA应用常见性能问题分析与优化插图22

三、Java应用CPU问题分析优化

1、现象: CPU占用高, 程序响应慢

要了解本身是否为CPU密集型,比如大量的计算等,以及被测服务器的核心数。

2、分析方法

1)保存dump文件

jstack <pid> >> thread.dump

2)找到导致CPU高的线程

top -H -p <pid>

3)Pid 十进制转十六进制

在线进制转换

4)找到对应的线程,打开 thread.dump文件

查找:按十六进制值
找到对应的线程,把相关的方法找出来,可以精确到代码的行
JAVA应用常见性能问题分析与优化插图24

5)Jvisualvm CPU 抽样器

JAVA应用常见性能问题分析与优化插图26

四、Java应用线程问题分析优化

1、保存堆栈信息

Jstack >> thead.dump

2、分析线程状态

cat thead.dump | grep ‘java.lang.Thread.State’ | awk ‘{print $2$3$4$5}’ | sort | uniq -c
JAVA应用常见性能问题分析与优化插图28

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)行
JAVA应用常见性能问题分析与优化插图30
  • 死锁(程序表现为无响应):线程状态为“waiting to lock”: 两个线程各持有一个锁,又在等待另一个锁,故造成死锁,且发生在DeadLockTest.java:39行
JAVA应用常见性能问题分析与优化插图32

发表评论