性能测试问题 初步定位方法&样例–java应用

2018年10月5日

 

1. 性能问题发现

1.1 用户体验

a. RT :   超时

b. TPS 率:达不到预期值

c.  错误率:比预期值高

以上数据来源为压测工具端的统计监控

1.2 被测端监控告警

资源层:系统 load 值、内存使用率、磁盘使用率、网络带宽等超过阈值

业务层:连接数满,full GC频率过高等

2.性能问题定位流程

 

 

3.  Java 类问题定位常用工具& 样例

Java类应用常见问题:

  • OutOfMemoryError,内存不足
  • 内存泄露
  • 线程死锁
  • 锁争用(Lock Contention)
  • Java进程消耗CPU过高
  • ……

3.1 问题定位工具

Jstat –gcutil [PID]
初步定位Java应用是否存在内存问题
查看JVM堆内存使用是否正常
使用MAT分析异常的内存dump(使用Jmap导出)

Jstack [PID]
初步定位Java应用是否存在线程问题
 dump 文件里,值得关注的线程状态有:

     死锁,Deadlock(重点关注)
     执行中,Runnable  
     等待资源,Waiting on condition(重点关注)
     等待获取监视器,Waiting on monitor entry(重点关注)
     暂停,Suspended
     对象等待中,Object.wait() 或 TIMED_WAITING
     阻塞,Blocked(重点关注) 
     停止,Parked

查看热点线程的调用树,判断可疑的业务代码
Jstack结果可视化查看工具——jca工具

jmap -dump:live,format=b,file=dump.hprof [PID]
使用MAT分析dump文件

GCLOG
稳定性测试、长时间测试时使用,记录Java应用内存变化趋势

Java 性能综合分析器–JMC,jconsole
初步判断是Java应用存在性能问题时,使用Jmc定位详细问题原因,监控项目很全,可以远程登录监控,但有点消耗资源;

对于线上问题排查,还是建议使用jstat,jstack,jmap工具等,结合top、vmstat等快速排查和定位问题。

—-Linux环境下:
运行命令的用户必须和Java进程运行的用户一致。应用使用root用户启动,必须使用root用户运行Java工具。

—Windows环境下:
运行工具的用户与Java进程运行的用户一致。
Windows用户体系与Linux不同,最高权限用户是System,Java应用已服务方式运行时,所属用户都是System。一般无法直接切换成System用户。
使用PStool的PsExec来实现获取System权限
在命令行中,使用PsExec.exe –s [Java工具绝对路径 Java工具参数]

3.2  案例1:RT大+CPU高 问题定位

现象1
在XX 应用执行一个批量(2000个)逻辑服务迁移时,平台10min没有响应。

平台后端服务为tomcat

定位1:查看监控–CPU
查看服务器资源利用情况(也可以直接通过Windows性能监视器或Zabbix之类的监控平台)

查看Tomcat进程CPU资源利用情况,tomcat的CPU利用率达到>180%(linux可以使用top -H)

找出该进程内最耗费CPU的线程,     top -Hp pid(top -Hp 19214),输出类似下图:

         

TIME列就是各个Java线程耗费的CPU时间,主要是19226和19227这两个线程消耗大量的CPU,而且时间累占用了一个小时左右,需要把线程号转换成十六进制,因为打印出来的堆栈信息里面的线程号是十六进制形式: printf “%x/n” 19226  —0x4bla,   printf “%x/n” 19226 7 —0x4blb

 

定位2:查看 JVM Heap监控
查看进程的JVM Heap的情况(可以用jstat ,或者直接查看JVM的heap监控)

jstat -gcutil 19214 1000 #19214是进程号

看到这里,每次fullgc消耗的时间,可以用相隔的两行FGCT相减得到。JVM一直在FullGC,进程被挂起,无法响应业务。

初步可以确定CPU消耗主要是由于JVM的FullGC引起;

 

定位3:是否是fullGC 线程消耗CPU? —查看 进程堆栈 和线程情况
快速定位线程造成的问题用:jstack [PID(进程号)] | grep [PID(线程号十六进制)] -A 10

从现象2的找到的线程ID转成十六进制0x4bla,查找线程对应的业务。

看到线程 19226, 19227果然是GC线程,是FullGC活动在消耗CPU

使用jstack  pid打印 进程堆栈信息

DB 连接线程处于 Timed_waiting状态(这个状态说明线程在等待唤醒,但设置了时限)

结合线程消耗CPU信息得出的结论是:Java程序连DB 连接相关的问题,导致GC线程一直占用CPU。

 

3.  案例2:RT 大+资源层无异常

现象:
并发登录时,通过监控发现在吞吐量达到100TPS时,打开首页和登出的响应时间突然快速升高(压测工具/平台)。

定位:
查看服务器的资源层监控,发现没有异常(CPU在35%,cpu load接近1,IO较小,load队列长度接近0)

由于操作系统层没有明显的异常,对应用层进程做分析

先看最外层应用tomcat,对tomcat抓了一个线程dump : jstack  pid

查看dump内容,发现有一个BLOCKED状态的线程
"http-nio-8080-exec-43" #149 daemon prio=5 os_prio=0 tid=0x0000000010947000 nid=0x2898 waiting for monitor entry [0x00000000273ac000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.io.PrintStream.write(PrintStream.java:478)
- waiting to lock <0x0000000080050520> (a java.io.PrintStream)
…
锁lock< 0x0000000080050520>如下
"http-nio-8080-exec-18" #123 daemon prio=5 os_prio=0 tid=0x00000000121c9000 nid=0x4b40 runnable [0x0000000025dad000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.write(BufferedOutputStream.java:122)
- locked <0x0000000080050540> (a java.io.BufferedOutputStream)----------------------here
at java.io.PrintStream.write(PrintStream.java:480)
- locked <0x0000000080050520> (a java.io.PrintStream)----------------------here
at org.apache.tomcat.util.log.SystemLogHandler.write(SystemLogHandler.java:177)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)at java.io.PrintStream.write(PrintStream.java:480)
- locked <0x0000000080050520> (a java.io.PrintStream)
at org.apache.tomcat.util.log.SystemLogHandler.write(SystemLogHandler.java:177)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295)
at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
- locked <0x0000000081ada7f0> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
at org.apache.log4j.helpers.QuietWriter.flush(QuietWriter.java:57)----------------------日志相关
at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:315)
at org.apache.log4j.WriterAppender.append(WriterAppender.java:159)
at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:230)
- locked <0x0000000081ad9bf8> (a org.apache.log4j.ConsoleAppender)
at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:65)
at org.apache.log4j.Category.callAppenders(Category.java:203)
- locked <0x0000000081ab9f28> (a org.apache.log4j.spi.RootLogger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.info(Category.java:663)

初步定位结论:从堆栈信息看,是写日志相关功能导致的线程阻塞,

 解决方案
经过开发定位,日志之前用的是同步写,后续将同步写日志改成异步写

没有评论

发表评论

邮箱地址不会被公开。 必填项已用*标注