手写了一个netty的client 对 我们的消息服务进行性能压测。
1、client端的逻辑是,利用netty的EventLoopGroup线程池进行模拟用户的connect,连接上之后,由于服务端的心跳的机制,如果用户的channel在12分钟内没有进站事件发生,那么服务端就会主动断开链接。
由于我们只有用户连接上服务端之后才可以发送消息,所以要发送两次消息,connect的时候发送一个上线通知。后续发送业务消息。
所以客户端另起一个定时线程池,考虑到测试机器的性能,核心线程数设置为20。启动2分钟后开始执行,每间隔2分钟执行一次。(jvm的-Xms4g -Xmx4g 时能测试到连接数为12w左右)。但是由于2分钟执行一次,导致服务端的cpu压不上去。
所以减少时间间隔,修改为每7s中执行一次,用户发消息的逻辑。
2、目前7s中执行一次发消息的逻辑,连接数到达3w的时候,发现jvm的堆内存已经满了。此时old 区占用整个堆内存的99%,如果继续压测会导致OOM。所以此时问题就来了。
问题:
为什么7分钟的时候old区会正常,但是7s的时候,到达2w连接数的时候就会导致old爆满?此时服务端的cpu也会降下来。
现象:
利用 jmap -heap pid查看内存情况
1、首先我们的项目用的是G1垃圾收集器,此时堆内空间并不是连续的,而是一个个的region。年轻带、幸存者区、老年代,都不是固定的。G1的目标就是减少Full GC,而此时,我们的服务基本停掉,一直不停的在执行Full GC。而到达这种状态的时候,我们的 S区和E区的region为零,而老年代几乎占据了所有的region----------------------> 说明此时存在大对象,导致了很多的内存碎片,所以导致新对象没有进入E区和S区直接进入了Old区,所以导致Old区内存不够用,就会晋升失败。
2、此时执行Full GC的时候的gc.log 一直显示 晋升失败
利用 jstat -gc pid 查看 gc 情况
3、发现full gc 次数频率很高,ygc频率更高,所以说明,每进来一个对象,就直接ygc了,然后因为old区内存不够用,所以就会直接full gc。
定位:
既然问题定位到了有大对象,此时有两种解决方案:
第一种:调大region的大小,让大对象不再是大对象。通过(XX:G1HeapRegionsize 设定)
第二种:排查代码
第一种方法,明显就是暂时的解决方案,所以我们可以用第二种。
步骤:
1、首先用 jmap -histo pid | head -20 查找前20名大对象。 或者 jamp -histo:live pid | head -20 查找前20名存活对象。
2、打印dump 文件 : jmap -F -dump:live,file=jmap.hprof [PID]
3、用eclipse 的 mat 进行分析。
4、根据dominator_tree找出大对象。
5、分析具体业务逻辑代码。
我的问题,是由于我们代码中有个,阻塞队列<200的话,就不删除ConcurrentHashMap当中的元素,而每次删除ConcurrentHashMap中的元素时,每次只删除1w条。所以就会在ConcurrentHashMap中慢慢累加元素,就会导致占用内存越来越多。导致大量的对象被ConcurrentHashMap引用无法释放,最后导致OOM。
所以我们netty的server的性能瓶颈就在这里,这也是特殊情况,因为我的客户端是以一定的速率进行发送消息的。而实际情况,用户发消息是又快又慢的。如果我们的量级真的达到了这种,可以适当调大线程池的核心线程数。