目录
一、问题概述
二、发现问题
三、解决问题
一、问题概述
在持续十分钟的压力测试中,单机QPS从8000陆续降低至3000的过程中发现的内存泄漏。
首先看一下压测计算的数据:
被压测的机器:
- 机器配置:4核8G
- 单机预计QPS:8000左右,不超过9000
- 平均响应时间:12~13ms
发起压测的执行机:
- 设置单机并发数:60
- 执行机数量:2台
由此可以计算出进行压测的QPS数据:
平均响应时间为12ms,单机60的并发数,则单机的最高QPS为 60/0.013 = 4600 左右
设置持续上涨4分钟,达到峰值,则可得出:
- 刚开始时并发量为:60 / 4 *2 = 30 的并发量,QPS = 30/0.013 = 2300
- 持续4min后:慢慢上涨到60*2的并发量,峰值 QPS = 120 / 0,013 = 9230
压力合理,可以缓慢达到压力的峰值并维持压力。
二、发现问题
整个压测过程分为2步,每步压测5分钟。压测完成后,查看压测报告:
首先是jmeter产出的报告:
看上去没有问题,达到了预期的效果,但是当对该机器进行第二次压测的时候,发现它的QPS已经下降了30%
第二次压测产出的报告:
这一次就有些困惑了,讲道理两次相同条件的压测结果差距不应该这么大,绝对是哪个地方有问题,导致qps上不去,像这种情况,多半是JVM的问题。
于是使用jdk自带的jvm监控工具对压测过程进行了监控。
将服务器重启一次,继续进行两步压测,这次从监控中直接看到了问题:
第一次报告:
GC时间报告:
很明显,jvm的状态是有问题的:
- 第一张图中的堆内存使用量居然下不来,说明了内存无法被回收,说明了存在内存泄漏的问题。
- 第二张图中显示老年代做GC的时间并不长,这应该是应该有的现象,这也是为什么本次压测QPS能到预期值
第二次报告:
GC时间报告:
看似JVM已经完成了GC,但是仔细看它的GC时间,老年代的GC整整持续了2分钟,这其实是不正常的。
这个时候打开JVM的内存选项,查看了老年代的jvm情况,发现它有整整2个G的内存无法被回收,而我这台机器总共才8个G。
(虽然jvm的老年代图弄丢了,但是从上面的图可以也可以看出来,它是有几个G的内存没有被回收的)
后面当我再对其进行压力测试,它的QPS直接降到了3000左右。而它的老年代GC时间一直在变长。
三、解决问题
上面已经找到了问题的原因,后面发现代码问题就很简单的
本项目是一个网关项目,内部有个linkHashMap需要存放某些数据,而该map内部做了舱壁限制了总map的size。但是linkHashMap是线程不安全的,代码中并没有对它的put操作进行加锁处理,导致它的内存不断膨胀,存放的数据不断的放入老年代,最后无法被老年代回收。
代码优化完成后,对它进行长时间的压测,查看它产出的报告:
这就很合理了,FullGC之后内存就降下来了,QPS又回到了8000。
为了测试它的稳定性,后续对它进行了持续两天的压力测试,没有出现性能问题。