
jstack那些事(一)
前言
工作多年,经常遇到灵异事件,比如说: "任务为什么不跑了" , "没什么复杂业务呀,怎么cpu这么高" ,"用了缓存,怎么查询还是这么慢" , 一些工作一两年的同学遇到这类问题可能会手足无措 ,所以我想写一些文字来和大家分享下 。文笔有限,主要是交流。 这篇是先和大家交流 jstack的用法,以及编程方面的一些建议。
1 经典场景
• 场景一 :任务(线程)突然不跑了
程序是一段简单的quartz 任务 ,伪代码类似:
查看日志可用查看到 “任务开始运行” 但后续没有打印“任务结束运行”
业务也没有正常处理 , 也没有异常 无奈之下 只好重启服务
• 场景二 cpu load 特别高 业务也不复杂
如下图:
业务场景是比分直播。
页面的逻辑是每隔几秒从memached 获取实时比赛信息 ,根据彩种类别获取数据,然后前端渲染。
页面访问 伪代码类似:
异常问题:页面非常卡顿 ,服务器上 cpu 利用率 和 load 非常高 。
2 jstack分析
jstack是jdk自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中线程堆栈信息。
执行命令类似:
有时 也可以采用 kill -3 命令 实现打印堆栈的效果
堆栈格式:
dump格式说明:
在jstack输出的第二行为线程的状态,在JVM中线程状态使用枚举 java.lang.Thread.State 来表示,State的定义如下:
在线程转换图:
jstack关键字分析
3 解决问题
3.1 线程阻塞问题
回到场景一 依靠上面的命令 我们打印出模拟的jstack堆栈
"Quartz-2" #1 prio=5 os_prio=31 tid=0x00007feb6c008000 nid=0x2603 runnable [0x000070000771a000] java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170) at java.net.SocketInputStream.read(SocketInputStream.java:141)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) at java.io.BufferedInputStream.read1(BufferedInputStream.java:286)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345) - locked <0x00000007959e1830> (a java.io.BufferedInputStream)
at sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:704) at sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:647)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1535) - locked <0x00000007959b1848> (a sun.net.www.protocol.http.HttpURLConnection)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1440) - locked <0x00000007959b1848> (a sun.net.www.protocol.http.HttpURLConnection)
at sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:2942)
at java.net.URLConnection.getContentType(URLConnection.java:512)
at com.courage.platform.sms.client.util.SmsHttpClientUtils.getResponseAsString(SmsHttpClientUtils.java:345)
at com.courage.platform.sms.client.util.SmsHttpClientUtils.doGet(SmsHttpClientUtils.java:241)
由上可见,当前线程阻塞在从http链接中获取响应数据。 可能对方httpserver耗时长,或者对方server hang住了 。针对这种情况,我适当的调整了 http client 的 connect 和read timeout。
3.2 cpu高问题
回到场景二,
我们先用ps命令找到对应进程的pid(如果你有好几个目标进程,可以先用top看一下哪个占用比较高)。
接着用
来找到cpu使用率比较高的一些线程 (下图是模拟图)
然后将占用最高的pid转换为16进制printf '%x\n' pid得到nid
接着直接在jstack中找到相应的堆栈信息jstack pid | grep 'nid' -C5 –color
可以看到我们已经找到了nid为0x42的堆栈信息。
比分直播 我们查询到占用cpu最高的是 GC 线程。那我们怀疑是由于GC 频率过高导致的。
然后使用 jstat 每隔1s查看内存使用情况
发现新生代每隔两秒就基本占满了 , 然后查看每个key对应的数据大小,发现每个value大小是500k左右 。
解决方式也比较简单 :
○减少缓存数据大小
○适当调整新生代的大小
○首次查询全量, 后续通过websocket推送增量给前端
