【性能测试】JAVA应用常见性能问题分析与优化

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

1. 性能分析基本流程

1.1 前提条件

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

1.2 性能问题的分类

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

1.3 案例

1.3.1 20路并发下,某接口的业务指标如下,对每个指标进行分析

在这里插入图片描述

  1. 事务失败占比–0%
    1. 本接口未出现失败数, 说明当前所有事务的返回结果 符合预期断言
    2. 如果有失败的情况, 需要结合最大响应时间判断,失败原因是因为超时还是业务处理失败
    3. 对于业务处理的失败,需要到业务日志中去查找,可能是因为并发导致异常
  2. TPS 31.5次/秒
    1. 1000/平均响应时间*并发数 ~ TPS
    2. 通过这个关系明确负载机, 性能脚本之间是否存在问题
    3. 期望值是: TPS > 并发数
  3. 平均响应时间 632ms(本公司要求指标是: 该路数并发下90%的响应时间)
    1. 以测试方案中的指标作为参考, 形成基本判断, 再分析响应构成
    2. 平均响应时间 > 方案中期望的时间: 不满足性能要求, 需要分析排查性能问题或者增加机器
    3. 平均响应时间 <= 方案中期望的时间: 满足性能要求, 但仍要分析排查是否有性能问题
    4. 平均响应时间 = 网络传输时间 + 系统处理时间
    5. 系统处理时间依赖业务日志统计: 平均响应时间 - 系统处理时间
      如果差距很大:
      (1)测试环境网络问题
      (2)带宽限制问题
      如果差距很小
      (1)分析系统处理时间
  4. 被测接口内部逻辑:
    在这里插入图片描述
    1. 统计整体耗时和重要RPC耗时
      在这里插入图片描述
    2. 耗时逻辑分析
      在这里插入图片描述
      在这里插入图片描述
      1. 外部调用耗时, 如数据库, 后端服务, 外部地址, 需要判断合理性
      2. 注意返回书籍大小对耗时的影响(测试环境可能出现带宽限制)
      3. 内部逻辑耗时, 深入到具体的方法调用, 比如序列化和反序列化
    3. 比如java处理json数据的三种方法: FastJSON, Gson和Jackson
      1. 序列化(object ==> json)
        在这里插入图片描述

      2. 反序列化(json ==> object)
        在这里插入图片描述

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

2. java应用内存分析以及优化

2.1 Java内存分配主要包括以下几个区域:

  1. 栈: 存放基本类型的数据和对象的引用, 但对象本身不存放在栈中, 而是存放在堆中
  2. 堆: 存放用new产生的数据
  3. 静态域: 存放在对象中用static定义的静态成员
  4. 常量池: 存放常量
    在这里插入图片描述
    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.2 Java常见的内存问题表现形式

  1. OutOfMemory: 内存溢出: 是指程序在申请内存时, 没有足够的内存空间供其使用
  2. MemoryLeak: 内存泄漏: 始终程序在申请内存后, 无法是否已申请的内存空间
2.2.1 导致OutOfMemoryError异常的常见原因有以下几种
  1. 内存中加载的数据量过于庞大, 如: 一次从数据库中取出过多数据
  2. 集合类中有对对象的引用, 使用完成后未清空, 使得jvm不能回收
  3. 代码中存在死循环或者循环过程中产生过多重复的对象实体
  4. 使用第三方软件中的BUG
  5. 启动参数内存值设定的过小
    (1)内存泄漏是导致内存溢出的原因之一, 内存泄漏积累起来将导致内存溢出
    (2)内存泄漏可以通过完善代码来避免, 内存溢出可以通过调整配置来减少发生频率, 但无法彻底避免

2.3 内存溢出类型

虚拟机栈溢出, 本地方法栈溢出, 方法区溢出, 堆溢出, 运行时常量池溢出

2.3.1 异常类型
  1. java.lang. OutOfMemoryError: Java heap space
    堆内存溢出
    优化: 通过-Xmm(最小值) -Xms(初始值) -Xmx(最大值) 参数手动设置Heap(堆)的大小
  2. java.lang.OutOfMemoryError: PermGen space
    PermGen Space溢出(方法区溢出, 运行时常量池溢出)
    优化: 通过MaxPermSize参数设置PermGen apsce大小
  3. java.lang.StackOverflowError
    栈溢出(虚拟机栈溢出, 本地方法栈溢出)
    优化: 通过Xss参数调整
2.3.2 堆内存分析
  1. 收集堆内存. 命令: jmap -dump:format=b,file=heap.dump <pid>
    或者 JVisualVM
    在这里插入图片描述
  2. 使用工具分析堆内存。 工具: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
    在这里插入图片描述

3. Java应用CPU问题分析优化

现象: CPU占用高, 程序响应慢
要了解本身是否为CPU密集型, 比如大量的计算等. 被测服务器的核心数

3.1 分析方法

  1. 保存dump文件。
    jstack <pid> >> thread.dump
  2. 找到导致CPU高的线程。
    top -H -p <pid>
  3. Pid 十进制转十六进制
    http://tool.oschina.net/hexconvert/
  4. 找到对应的线程,打开 thread.dump文件
    查找:按十六进制值
    找到对应的线程,把相关的方法找出来,可以精确到代码的行.
    在这里插入图片描述
    Jvisualvm CPU 抽样器
    在这里插入图片描述

4. Java应用线程问题分析优化

  1. 保存堆栈信息。
    Jstack >> thead.dump
  2. 分析线程状态
    cat thead.dump | grep ‘java.lang.Thread.State’ | awk ‘{print $2$3$4$5}’ | sort |
    uniq -c
    在这里插入图片描述

4.1 线程状态

  1. RUNNABLE: 线程正在执行中,占用了资源,比如处理某个请求/进行
    计算/文件操作等
  2. BLOCKED/Waiting to lock(需关注)
    1. 线程处于阻塞状态,等待某种资源(可理解为等待资源超时的线程);
    2. “waiting to lock <xxx>”,即等待给xxx上锁,grep stack文件找locked <xxx> 查找获得锁的线程;
    3. “waiting for monitor entry” 线程通过synchronized(obj){……}申请进入了临界区,但该obj对应的monitor被其他线程拥有,从而处于等待。
  3. WAITING/TIMED_WAITING{定时}(关注)
  4. Deadlock(需关注):死锁,资源相互占用
  5. IO阻塞(程序表现为响应慢,Load高)
    线程状态为“in Object.wait()”,说明正在等待线程池可用资源,由于线程池满导致新的IO请求处于排队等待状态,且发生在:at com.iflytek.diange.data.provider.sendsong.impl.SendSongImpl.getSendSongInfosByUserId(SendSongImpl.java:92)行
    在这里插入图片描述
  6. 死锁(程序表现为无响应)
    线程状态为“waiting to lock”: 两个线程各持有一个锁,又在等待另一个锁,故造成死锁,且发生在DeadLockTest.java:39行
    在这里插入图片描述
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值