性能测试常见问题分析
一、内存溢出
1.堆内存溢出
堆(heap):是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),C中的malloc语句所产生的内存就在堆中。
在java中,所有使用new xxx()构造出来的对象都在堆中存储,当垃圾回收检测到某对象未被引用,则自动销毁该对象,所以,理论上说java中对象的生存空间是没有 限制的,只要有引用类型指向他,则它就可以再任意地方被使用。
问题:压测执行一段时间后,系统处理能力下降,这时使用JConsole、JVisualVM等工具连上服务器查看GC情况,每次GC回收不彻底,可用内存越来越少;持续压测最终报错:java.lang.OutOfMemoryError.Java heap space。
分析:(1) 使用jmap -histo pid > test.txt命令将堆内存使用情况保存到test.txt文件中,打开文件查看排在前50的类中有没有熟悉的类名,如果有则怀疑内存泄漏时这个类导致的。
(2) 如果没有,则使用命令:jmap -dump:live,format=b,file=test.dump pid 生成test.dump文件,然后使用MAT进行分析。
(3) 如果怀疑是内存泄漏,也可以使用JProfiler连上服务器再开始爬压测,运行一段时间后点击"Mark Current Values",后续的运行就会显示增量,这时执行以下GC,观察哪个类没有彻底回收,基本就可以判断是这个类导致的内存泄漏。
解决方式:代码优化,对象使用完毕,需要置成null。
3.栈内存溢出
栈(stack):是一个先进后出的数据结构,通常用于保存方法(函数)中的参数,局部变量。
在java中,所有基本类型和引用类型都在栈中存储,栈中数据在生存永健一般都在scopes内(就是由{}括起来的区域)。
问题:压测执行一段时间后,日志报错:java.lang.StackOverflowError。
分析:线程请求的栈深度大于虚拟机所允许的最大深度,递归没有返回,或者循环调用造成。
解决方式:修改JVM参数,将Xss参数改大,增加栈内存,栈内存溢出一定是做批量操作引起的,减少批处理数据量。
3.持久代溢出
问题:压测执行一段时间后,日志报错:java.lang.OutOfMemoryError: PermGen space。
分析:由于类、方法描述、字段描述、常量池、访问修饰符等一些静态变量太多,将持久带占满导致持久代溢出。
解决方式:修改JVM参数,将XX:MaxPermSize参数调大,尽量减少静态变量。
4.系统内存溢出
问题:压测执行一段时间后,日志报错:java.lang.OutOfMemoryError: unable to create new native thread。
分析:操作系统没有祖国的资源来产生返回线程造成的,堆中或许还有空间,但是操作系统分配不出资源来了,就报这个异常。
解决方式:1.减少堆内存。2.较少线程数量。3.如果线程数量不能减少,则减少每个线程的堆大小,通过-Xss减小单个线程大小,以便能生产更多的线程。4.增加系统内存。
5.java直接内存溢出
问题:压测执行一段时间后,日志报错:OutOfMemoryError。
分析:(1) 直接内存大多时候也被称为堆外内存,直接内存通过native方法可以分配堆外内存,通过DirectByteBuffer对象来操作。直接内存不属于Java堆,所以它不受堆内存大小限制,但是它受物理内存大小限制。
(2) 可以通过-XX:MaxDirectMemorySize参数来设置最大可用直接内存,如果启动时未设置则默认为最大堆内存大小,即与-Xmx相同。即加入最大堆内存为1G,则默认直接内存也为1G,那么JVM最大需要的内存大小为2G多一点。当直接内存达到最大限制时就会组发GC,如果回收失败则会引起OutOfMemoryError。
(3) 直接内存在读和写的性能都优于堆内存,但是内存申请速度却不如堆内存。
解决方式:直接内存适用于需要大内存空间且访问频繁的场合,不适用于频繁申请释放内存的场合,在需要频繁申请的场景下不应该使用直接内存(DirectMemory),而应该使用堆内存(HeapMemory)。
二、CPU使用率过高
1.us cpu高
问题:压测过程中,使用top命令查看系统资源占用情况,us cpu使用率过高,超过70%。
分析:(1) 使用top命令查看是哪个进程消耗CPU高。
(2) 再找到CPU消耗高的线程:top -H -p 进程号。
(3) 把线程号转换成16进制:printf "%x\n" 线程号。
(4) 再用jstack命令分析这个线程是在干什么:jstack 进程号 | grep 16进制的线程号。
(5) 通过JProfiler的CPU Views视图的层层分析,可以清楚的找到造成CPU高的原因。
解决方式:代码优化,换配置好的CPU等。
2.sy cpu高
问题:压测过程中,使用top命令查看系统资源占用情况,sy cpu使用率过高,超过70%。
分析:(1) 查看磁盘繁忙程度,磁盘队列(iostat、nmon)。
(2) 如果磁盘没有问题,则使用strace查看系统内核调用情况。
解决方式:换配置好的CPU等。
三、线程死锁
问题:压测过程中,程序停顿,报超时错误。这种线上并不一定就是线程死锁引起的,也可能是数据库/中间件连接池被占满,数据库死锁引起的。
能够打开页面,但获取不到数据。
分析:(1) 使用jstack命令查看Java进程下所有线程的情况:jstack -l 进程号。
(2) 如果有Blocked状态的进程,说明有线程死锁的情况。如果大量线程都是Waiting状态,则需要去关注数据库和中间件,可能会有排队情况。
(3) 也可以使用JCconsole、JVisualVM及JProfiler等哦工具直接查看所有线程的情况。
四、数据库连接池不释放
问题:压测进行一段时间后,报连接超时的错误。
分析:(1) 去数据库查看应用程序到数据库的连接有多少:show full processlist。假如应用程序中配置的最大连接池为30,而通过命令show full processlist查看到的从应用服务器连接过来的连接数也为30,说明数据库连接池被占满了。
(2) 将应用程序中的最大连接数改大一点(比如100),再冲洗进行压测,如果还是出现连接池被占满的情况,这就可能是数据库连接池不释放造成的。
解决方式:排查代码,数据库连接部分应该有常见连接但没有关闭连接的情况。
五、数据库死锁
问题:压测进行一段时间后,报连接超时的错误。
程序在执行的过程中,点击确定或保存按钮,程序没有反应,也没有出现报错。
分析:(1) 查看数据库日志,看有没有死锁的情况:show engine innodb status\G。
解决方式:排查代码。
六、SQL使用不合理
问题:事物响应时间慢。
分析:(1) 打开数据库的慢查询。
(2) 通过命令找到执行时间比较常的SQL语句:mysqldumpslow。
解决方式:优化sql语句。
七、TPS上不去
1. 网络带宽
在压力测试中,有时候要模拟大量的用户请求,如果单位时间内传递的数据包过大,超过了带宽的传输能力,那么就会造成网络资源竞争,间接导致服务器端接收的请求数达不到服务器端的处理能力。
2. 连接池
最大连接数太小,造成请求等待,连接池一般分为服务器中间件连接池(tomcat)和数据库连接池。
3. 垃圾回收机制
从常见的应用服务器来说,比如tomcat,如果堆内存设置比较小,就会造成新生代的Eden区频繁的进行YoungGC,老年代的Full GC也回收的比较频繁,那么对TPS也是有一定影响的,因为垃圾回收时通常会暂停所有线程的工作。
4. 数据库
高并发情况下,如果请求数据需要写入数据库,且需要写入多个表的时候,如果数据库的最大连接数不够,或者写入数据的sql没有索引,没有绑定变量,或者没有主从分离,读写分离等,就会导致数据库事物处理过慢,影响到TPS。
5. 压力机
比如Jmeter和Loadrunner,单机负载能力有限,如果需要模拟的用户请求数超过其负载极限,也会间接影响TPS,这时候就需要进行分布式压测来解决其单机负载的问题。
6. 硬件资源
包括CPU(配置,使用率等)、内存(占用率等)、磁盘(I/O、页交换等)。
7. 业务逻辑
业务解耦度较低,较为复杂,整个事务处理线被拉长也会导致TPS上不去。
8. 系统架构
比如是否有缓存服务,缓存服务器配置,缓存命中率,缓存穿透以及缓存过期等,都会影响到测试结果。

浙公网安备 33010602011771号