jstat和jmap使用
背景
最近在做Spring Websocket后臺(tái)程序的壓力測(cè)試,但是當(dāng)并發(fā)數(shù)目在10個(gè)左右時(shí),服務(wù)器的CPU使用率一直在160%+,出現(xiàn)這個(gè)問題后,一開始很納悶,雖然服務(wù)器配置很低,但也不至于只有10個(gè)并發(fā)吧。。服務(wù)器的主要配置如下:
- CPU:2核 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
- 內(nèi)存:4GB
使用top命令查看資源占用情況,發(fā)現(xiàn)pid為9499的進(jìn)程占用了大量的CPU資源,CPU占用率高達(dá)170%,內(nèi)存占用率也達(dá)到了40%以上。?
問題排查
首先使用jstat命令來查看一下JVM的內(nèi)存情況,如下所示:
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區(qū)域內(nèi)存占用高達(dá)100%,Old區(qū)占用高達(dá)94.9%,元數(shù)據(jù)空間區(qū)域占用高達(dá)97.4%。Young GC的次數(shù)一直是24,但是Full GC的次數(shù)卻高達(dá)幾千次,而且在程序運(yùn)行期間,頻繁發(fā)生Full GC,導(dǎo)致FGC的次數(shù)一直增加。?
雖然FGC次數(shù)一直在增加,但是卻沒有回收到任何空間,導(dǎo)致一直在運(yùn)行FGC,根據(jù)這些信息,基本可以確定是程序代碼上出現(xiàn)了問題,可能存在內(nèi)存泄漏問題,或是創(chuàng)建了不合理的大型對(duì)象。
基于上述分析,我們知道應(yīng)該是程序的問題,要定位問題,我們需要先獲取后臺(tái)程序的堆轉(zhuǎn)儲(chǔ)快照,我們使用jmap工具來生成Java堆轉(zhuǎn)儲(chǔ)快照:
jmap -dump:live,format=b,file=problem.bin 9499Dumping heap to /root/problem.bin ... Heap dump file created- 1
- 2
- 3
- 4
下面就是對(duì)Java堆轉(zhuǎn)儲(chǔ)快照進(jìn)行分析了,我使用了Eclipse Memory Analyzer(MAT)來對(duì)快照進(jìn)行分析,在MAT打開快照文件之前,要將其后綴名修改為hprof,打開文件之后,可以發(fā)現(xiàn)如下問題:
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的實(shí)例占用了75.57%的內(nèi)存空間,而這也就是問題所在了,那WsFrameServer為什么會(huì)占用這么高的內(nèi)存呢?我繼續(xù)用MAT來查看WsFrameServer實(shí)例的內(nèi)存分布情況:?
?
?
可以看到,WsFrameServer實(shí)例中,有兩個(gè)類型的變量占了WsFrameServer的絕大部分,它們分別是java.nio.HeapCharBuffer類的實(shí)例變量messageBufferText、java.nio.HeapByteBuffer類的實(shí)例變量messageBufferBinary。
WsFrameServer繼承自WsFrameBase ,messageBufferText和messageBufferBinary屬性就在WsFrameBase里,然后我們來debug程序,看看這兩個(gè)屬性是如何被賦值的。
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結(jié)果:?
可以看到,這兩個(gè)變量的capacity都是20971520,它們是根據(jù)WsSession返回的大小來分配大小的,我們來看WsSession的方法的返回值:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
這兩個(gè)變量的大小默認(rèn)都是8192,那如果它們只占用8K的內(nèi)存大小,應(yīng)該也不會(huì)出現(xiàn)問題啊,那這兩個(gè)變量一定是在其他地方被修改了,我們繼續(xù)看源代碼,在WsSession的構(gòu)造方法中有如下兩行代碼:
this.maxBinaryMessageBufferSize = webSocketContainer.getDefaultMaxBinaryMessageBufferSize(); this.maxTextMessageBufferSize = webSocketContainer.getDefaultMaxTextMessageBufferSize();- 1
- 2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
webSocketContainer是在WsSession的構(gòu)造方法中傳入的,webSocketContainer這兩個(gè)方法分別返回maxBinaryMessageBufferSize和maxTextMessageBufferSize的值,它們默認(rèn)為:
private int maxBinaryMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE; private int maxTextMessageBufferSize = Constants.DEFAULT_BUFFER_SIZE;- 1
- 2
即這兩個(gè)方法的默認(rèn)返回值仍然是Constants.DEFAULT_BUFFER_SIZE,即8192,那它們是在哪里改變成20971520了呢??
WsWebSocketContainer類中還有以下幾個(gè)方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
這幾個(gè)方法分別可以獲取和設(shè)置maxBinaryMessageBufferSize和maxTextMessageBufferSize的值,那是不是通過這幾個(gè)方法來修改的值呢??
ServletServerContainerFactoryBean類中有如下一段代碼:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
這個(gè)方法將在bean所有的屬性被初始化后調(diào)用,其實(shí)這兩個(gè)值就是在這修改的了。?
為什么這么說呢,我們看著兩個(gè)截圖:?
對(duì)比這兩張圖片可知,WsSession的構(gòu)造方法中傳入的wsWebSocketContainer與項(xiàng)目啟動(dòng)時(shí)的serverContainer是同一個(gè)實(shí)例。所以,在afterPropertiesSet()方法中設(shè)置的值就是在wsWebSocketContainer中設(shè)置的值。
ServletServerContainerFactoryBean類的相關(guān)屬性如下:
@Nullable private Integer maxTextMessageBufferSize;@Nullable private Integer maxBinaryMessageBufferSize;- 1
- 2
- 3
- 4
- 5
這兩個(gè)屬性的初始值是在servlet中設(shè)置的:
<bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean"><property name="maxTextMessageBufferSize" value="20971520"/><property name="maxBinaryMessageBufferSize" value="20971520"/> </bean>- 1
- 2
- 3
- 4
總結(jié)
通過上述分析,也就解釋了為什么WsFrameServer占用了很大的內(nèi)存。那程序中為什么一開始將這兩個(gè)值設(shè)置這么大呢?原因是在很久以前,我們剛測(cè)試Websocket通信時(shí),發(fā)現(xiàn)只能傳輸小于8K的消息,大于8K的消息都不能進(jìn)行傳輸,所以我們干脆把它調(diào)大,也就直接設(shè)置為了20M,這也就導(dǎo)致了現(xiàn)在的這個(gè)問題。?
但是程序中發(fā)送的消息大小都是100K+的,那我也不能將他們?cè)O(shè)置太小,所以我們將其改小,設(shè)置為200K,然后重新測(cè)試,能夠達(dá)到50并發(fā)。但是,50并發(fā)感覺還是不太行,不知道能不能有其他的解決辦法~_~我再想想。
總結(jié)
以上是生活随笔為你收集整理的jstat和jmap使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云域名备案
- 下一篇: Java并发编程实战(chapter_3