背景

最近在做Spring Websocket后台程序的压力测试,但是当并发数目在10个左右时,服务器的CPU使用率一直在160%+,出现这个问题后,一开始很纳闷,虽然服务器配置很低,但也不至于只有10个并发吧。。服务器的主要配置如下:

  • CPU:2核 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
  • 内存:4GB

使用top命令查看资源占用情况,发现pid为9499的进程占用了大量的CPU资源,CPU占用率高达170%,内存占用率也达到了40%以上。 

问题排查

首先使用jstat命令来查看一下JVM的内存情况,如下所示:

jstat -gcutil 9499 1000S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   0.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8129 1147.010 1147.6610.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8136 1148.118 1148.7680.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8143 1149.139 1149.7890.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8150 1150.148 1150.7990.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8157 1151.160 1151.8110.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8164 1152.180 1152.8310.00   0.00 100.00  94.92  97.44  95.30     24    0.651  8170 1153.051 1153.7010.00   0.00 100.00  94.92  97.45  95.30     24    0.651  8177 1154.061 1154.7120.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8184 1155.077 1155.7280.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8191 1156.089 1156.7390.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8198 1157.134 1157.7850.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8205 1158.149 1158.8000.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8212 1159.156 1159.8070.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8219 1160.179 1160.8300.00   0.00 100.00  94.93  97.45  95.30     24    0.651  8225 1161.047 1161.697

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可以看到,Eden区域内存占用高达100%,Old区占用高达94.9%,元数据空间区域占用高达97.4%。Young GC的次数一直是24,但是Full GC的次数却高达几千次,而且在程序运行期间,频繁发生Full GC,导致FGC的次数一直增加。 
虽然FGC次数一直在增加,但是却没有回收到任何空间,导致一直在运行FGC,根据这些信息,基本可以确定是程序代码上出现了问题,可能存在内存泄漏问题,或是创建了不合理的大型对象。


基于上述分析,我们知道应该是程序的问题,要定位问题,我们需要先获取后台程序的堆转储快照,我们使用jmap工具来生成Java堆转储快照:

jmap -dump:live,format=b,file=problem.bin 9499Dumping heap to /root/problem.bin ...
Heap dump file created
  • 1
  • 2
  • 3
  • 4

下面就是对Java堆转储快照进行分析了,我使用了Eclipse Memory Analyzer(MAT)来对快照进行分析,在MAT打开快照文件之前,要将其后缀名修改为hprof,打开文件之后,可以发现如下问题:

9 instances of "org.apache.tomcat.websocket.server.WsFrameServer", loaded by "java.net.URLClassLoader @ 0xc533dc70" occupy 566,312,616 (75.57%) bytes. Biggest instances:
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xce4ef270 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xce4f1588 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xcf934b10 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xcf936e28 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xcf9620f8 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xd21c6158 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xd5dc8b30 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xd727bcf8 - 62,923,624 (8.40%) bytes.
•org.apache.tomcat.websocket.server.WsFrameServer @ 0xe768bd68 - 62,923,624 (8.40%) bytes.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

可以看到WsFrameServer的实例占用了75.57%的内存空间,而这也就是问题所在了,那WsFrameServer为什么会占用这么高的内存呢?我继续用MAT来查看WsFrameServer实例的内存分布情况: 

 
 
可以看到,WsFrameServer实例中,有两个类型的变量占了WsFrameServer的绝大部分,它们分别是java.nio.HeapCharBuffer类的实例变量messageBufferText、java.nio.HeapByteBuffer类的实例变量messageBufferBinary。

WsFrameServer继承自WsFrameBase ,messageBufferText和messageBufferBinary属性就在WsFrameBase里,然后我们来debug程序,看看这两个属性是如何被赋值的。

public WsFrameBase(WsSession wsSession, Transformation transformation) {inputBuffer = ByteBuffer.allocate(Constants.DEFAULT_BUFFER_SIZE);inputBuffer.position(0).limit(0);messageBufferBinary = ByteBuffer.allocate(wsSession.getMaxBinaryMessageBufferSize());messageBufferText = CharBuffer.allocate(wsSession.getMaxTextMessageBufferSize());wsSession.setWsFrame(this);this.wsSession = wsSession;Transformation finalTransformation;if (isMasked()) {finalTransformation = new UnmaskTransformation();} else {finalTransformation = new NoopTransformation();}if (transformation == null) {this.transformation = finalTransformation;} else {transformation.setNext(finalTransformation);this.transformation = transformation;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们首先看debug结果: 


可以看到,这两个变量的capacity都是20971520,它们是根据WsSession返回的大小来分配大小的,我们来看WsSession的方法的返回值:

private volatile int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
private volatile int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;static final int DEFAULT_BUFFER_SIZE = Integer.getInteger("org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE", 8 * 1024).intValue();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这两个变量的大小默认都是8192,那如果它们只占用8K的内存大小,应该也不会出现问题啊,那这两个变量一定是在其他地方被修改了,我们继续看源代码,在WsSession的构造方法中有如下两行代码:

this.maxBinaryMessageBufferSize = webSocketContainer.getDefaultMaxBinaryMessageBufferSize();
this.maxTextMessageBufferSize = webSocketContainer.getDefaultMaxTextMessageBufferSize();
  • 1
  • 2
@Override
public int getDefaultMaxBinaryMessageBufferSize() {return maxBinaryMessageBufferSize;
}@Override
public int getDefaultMaxTextMessageBufferSize() {return maxTextMessageBufferSize;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

webSocketContainer是在WsSession的构造方法中传入的,webSocketContainer这两个方法分别返回maxBinaryMessageBufferSize和maxTextMessageBufferSize的值,它们默认为:

private int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;
  • 1
  • 2

即这两个方法的默认返回值仍然是Constants.DEFAULT_BUFFER_SIZE,即8192,那它们是在哪里改变成20971520了呢? 
WsWebSocketContainer类中还有以下几个方法:

@Override
public int getDefaultMaxBinaryMessageBufferSize() {return maxBinaryMessageBufferSize;
}@Override
public void setDefaultMaxBinaryMessageBufferSize(int max) {maxBinaryMessageBufferSize = max;
}@Override
public int getDefaultMaxTextMessageBufferSize() {return maxTextMessageBufferSize;
}@Override
public void setDefaultMaxTextMessageBufferSize(int max) {maxTextMessageBufferSize = max;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这几个方法分别可以获取和设置maxBinaryMessageBufferSize和maxTextMessageBufferSize的值,那是不是通过这几个方法来修改的值呢? 
ServletServerContainerFactoryBean类中有如下一段代码:

public void afterPropertiesSet() {Assert.state(this.servletContext != null,"A ServletContext is required to access the javax.websocket.server.ServerContainer instance");this.serverContainer = (ServerContainer) this.servletContext.getAttribute("javax.websocket.server.ServerContainer");Assert.state(this.serverContainer != null,"Attribute 'javax.websocket.server.ServerContainer' not found in ServletContext");if (this.asyncSendTimeout != null) {this.serverContainer.setAsyncSendTimeout(this.asyncSendTimeout);}if (this.maxSessionIdleTimeout != null) {this.serverContainer.setDefaultMaxSessionIdleTimeout(this.maxSessionIdleTimeout);}if (this.maxTextMessageBufferSize != null) {this.serverContainer.setDefaultMaxTextMessageBufferSize(this.maxTextMessageBufferSize);}if (this.maxBinaryMessageBufferSize != null) {this.serverContainer.setDefaultMaxBinaryMessageBufferSize(this.maxBinaryMessageBufferSize);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这个方法将在bean所有的属性被初始化后调用,其实这两个值就是在这修改的了。 
为什么这么说呢,我们看着两个截图: 

对比这两张图片可知,WsSession的构造方法中传入的wsWebSocketContainer与项目启动时的serverContainer是同一个实例。所以,在afterPropertiesSet()方法中设置的值就是在wsWebSocketContainer中设置的值。

ServletServerContainerFactoryBean类的相关属性如下:

@Nullable
private Integer maxTextMessageBufferSize;@Nullable
private Integer maxBinaryMessageBufferSize;
  • 1
  • 2
  • 3
  • 4
  • 5

这两个属性的初始值是在servlet中设置的:

<bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean"><property name="maxTextMessageBufferSize" value="20971520"/><property name="maxBinaryMessageBufferSize" value="20971520"/>
</bean>
  • 1
  • 2
  • 3
  • 4

总结

通过上述分析,也就解释了为什么WsFrameServer占用了很大的内存。那程序中为什么一开始将这两个值设置这么大呢?原因是在很久以前,我们刚测试Websocket通信时,发现只能传输小于8K的消息,大于8K的消息都不能进行传输,所以我们干脆把它调大,也就直接设置为了20M,这也就导致了现在的这个问题。 
但是程序中发送的消息大小都是100K+的,那我也不能将他们设置太小,所以我们将其改小,设置为200K,然后重新测试,能够达到50并发。但是,50并发感觉还是不太行,不知道能不能有其他的解决办法~_~我再想想。

jstat和jmap使用相关推荐

  1. 虚拟机的性能监控与故障处理——jps,jstat,jinfo,jmap,jhat,jstack

    Java虚拟机实现了内存自动分配,垃圾回收机制,但是这一过程,究竟什么时候执行,执行到什么地步,却需要jdk提供的一些工具来监控. 比如jps,jstat,jinfo,jmap,jhat,jstack ...

  2. JDK自带VM分析工具jps,jstat,jmap,jconsole

    一.概述 SUN 的JDK中的几个工具,非常好用.秉承着有免费,不用商用的原则.以下简单介绍一下这几种工具.(注:本文章下的所有工具都存在JDK5.0以上版本的工具集里,同javac一样,不须特意安装 ...

  3. JDK自带监控工具 jps、jinfo、jstat、jmap、jconsole

    分类: JVM 2010-10-04 11:05 587人阅读 评论(0) 收藏 举报 工具jdkjava远程连接unixstring 常用有五个命令行工具: jinfo: 可以输出并修改运行时的ja ...

  4. JVM分析指令解析-jps/jinfo/jstat/jstack/jmap/jcmd

    本文主要解析JVM分析指令-jps/jinfo/jstat/jstack/jmap jps 英文全名: Java Virtual Machine Process Status Tool 功能: Jav ...

  5. JVM调优:运行参数,内存模型,mat、jps、jstat、jmap、jstack、jvisualvm工具的使用

    JVM调优 - 工具篇 作者:张学亮 讲解内容 了解下我们为什么要学习JVM优化 掌握jvm的运行参数以及参数的设置 掌握jvm的内存模型(堆内存) 掌握jamp命令的使用以及通过MAT工具进行分析 ...

  6. jps,jstat,jinfo,jmap,jhat,jstack工具的使用/查看Linux磁盘信息

    1.查看磁盘还剩多少空间,使用df命令(查看Linux版本:lsb_release -a,uname -a) 2.当前文件夹下的磁盘使用情况:(du --max-depth=1 -h后面没有显示跟路径 ...

  7. java虚拟机内存监控_java虚拟机内存监控工具jps,jinfo,Jstack,jstat,jmap,jhat使用...

    将会打印出很多jvm运行时参数信息,由于比较长这里不再打印出来,可以自己试试,内容一目了然 Jstack(Stack Trace for Java):JVM堆栈跟踪工具 jstack用于打印出给定的j ...

  8. jinfo java_Java自带的JVM性能监控及调优工具(jps、jinfo、jstat、jmap、javap)使用介...

    JVM介绍 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. ...

  9. 现网问题排查实战:Jstat,Jstack,Jmap

    遇到问题怎么办: 查看日志tail -f catalina.out 查看应用进程 ps -ef | grep java 查看CPU情况 查看TCP情况 ping 查看java线程,jstack 查看j ...

最新文章

  1. sql左连接_【PL/SQL 练习题】左连接条件里的And和Where
  2. 最新CAX/EDA/CFD/GIS/光学/化工/液压软件资源网
  3. 2018-2019-1 20165226 《信息安全系统设计基础》第8周学习总结
  4. python自学视频-师傅带徒弟学Python:第一篇Python基础视频课程
  5. 【PAT - 1014】福尔摩斯的约会(简单模拟)
  6. kfcm算法matlab实现,KFCM算法分析
  7. 【Java】RuleSource约束常用方法整理
  8. 【转载】利用压缩网页来提升网站浏览速度
  9. 华谊兄弟:拟向阿里影业、腾讯等发行不超8.2亿股股票
  10. 如何判断笔记本蓝牙硬件坏了_还在担心被套路?老司机教你如何判断车用尿素溶液的好与坏...
  11. 用Python编写干净 可测试 高质量的代码
  12. 北京python程序员求职_想找python程序员的工作,但发现稍微好点的职位都集中在北京。我非常想当python程序员,北京值得去吗?...
  13. Hive 存储格式入门
  14. 带进度条的Flash多文件上传面板(SwfUploadPanel) (转载)
  15. 巧替换windows 7中的宋体 simsun.ttc
  16. 批量生成ip地址shell脚本
  17. python重新安装ssl_python3安装文件遇到ssl未安装问题
  18. 万豪集团将在苏州引入丽思卡尔顿和万豪行政公寓双品牌项目;美联航等成立合资企业开发新可持续航空燃料技术 | 美通企业日报...
  19. STL(标准模板库)—Vector
  20. 任务卡片优先级排序-Leangoo看板工具

热门文章

  1. 《C程序设计语言》- 字符输入和输出
  2. 解决三星 BIOS 模式没有 Fast Bios Mode选项 U盘动项问题
  3. 快讯|工业大数据产业发展联盟成立,助力我国产业生态集聚
  4. Delphi制作图像特殊显示效果
  5. 门户网站负载均衡技术的六大新挑战
  6. 什么是Activity 和 Activity分类
  7. ubuntu启动时自动挂载windows分区
  8. 如何实现Asp与Asp.Net共享Session
  9. mysql 问号作用_什么是MySQL中的问号的意义“WHERE column =?”?
  10. hibernate映射一对多双向关联关系实例