jstack那些事(一)

iamwaiwai
发布于 2022-6-27 17:19
浏览
0收藏

前言

 

工作多年,经常遇到灵异事件,比如说: "任务为什么不跑了" , "没什么复杂业务呀,怎么cpu这么高" ,"用了缓存,怎么查询还是这么慢" , 一些工作一两年的同学遇到这类问题可能会手足无措 ,所以我想写一些文字来和大家分享下 。文笔有限,主要是交流。 这篇是先和大家交流 jstack的用法,以及编程方面的一些建议。 

 

1 经典场景


 • 场景一 :任务(线程)突然不跑了

程序是一段简单的quartz 任务 ,伪代码类似:

public class DataJobs

{   public void run() {  

 log.info("任务开始运行");  

 //准备数据    

Object prepareData = prepare();  

 //调用http远程接口获取    

Result result = getHttpRemoteData(prepareData);

   //存储    store(result);  

 log.info("任务结束运行");  

}}

 

查看日志可用查看到 “任务开始运行”   但后续没有打印“任务结束运行” 

业务也没有正常处理 , 也没有异常  无奈之下 只好重启服务 

 

 • 场景二 cpu load 特别高  业务也不复杂  
如下图:

jstack那些事(一)-鸿蒙开发者社区

业务场景是比分直播。

页面的逻辑是每隔几秒从memached 获取实时比赛信息 ,根据彩种类别获取数据,然后前端渲染。

页面访问 伪代码类似:

 @RequestMapping("/getLiveData")

@ResponseBody

public String getLiveData(String lotteryCode) {    

//从memcache中获取比分直播数据  仅仅是一个 get key from memcahed    

String result = getDataFromMemcache(lotteryCode);    

 return result; }

异常问题:页面非常卡顿 ,服务器上 cpu 利用率 和 load 非常高 。

 

2 jstack分析 


jstack是jdk自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中线程堆栈信息。

jstack那些事(一)-鸿蒙开发者社区

执行命令类似: 

jstack -l pid >> statck.txt

 

有时 也可以采用 kill -3 命令 实现打印堆栈的效果 

kill  -3  pid

堆栈格式: 

"redisson-netty-2-4" #37 prio=5 os_prio=0 tid=0x00007f91ae738000 nid=0x3bf1 runnable [0x00007f91a5b4d000]

 java.lang.Thread.State: RUNNABLE      

at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)    

 at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)    

 at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)      

at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)      

- locked <0x00000000ee106580> (a io.netty.channel.nio.SelectedSelectionKeySet)    

 - locked <0x00000000ee1065b0> (a java.util.Collections$UnmodifiableSet)    

 - locked <0x00000000edff8448> (a sun.nio.ch.EPollSelectorImpl)      

at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)    

 at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)    

 at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:752)      

at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)    

at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)      

at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)      

at java.lang.Thread.run(Thread.java:748)
   Locked ownable synchronizers:        

- None

dump格式说明: 

jstack那些事(一)-鸿蒙开发者社区

在jstack输出的第二行为线程的状态,在JVM中线程状态使用枚举 java.lang.Thread.State 来表示,State的定义如下:

 public enum State {        

NEW,        

RUNNABLE,        

BLOCKED,        

WAITING,      

 TIMED_WAITING,        

TERMINATED;    

}

在线程转换图: 

jstack那些事(一)-鸿蒙开发者社区

jstack关键字分析

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看一下哪个占用比较高)。
接着用

top -H -p pid

 

来找到cpu使用率比较高的一些线程 (下图是模拟图

jstack那些事(一)-鸿蒙开发者社区

然后将占用最高的pid转换为16进制printf '%x\n' pid得到nid

jstack那些事(一)-鸿蒙开发者社区

接着直接在jstack中找到相应的堆栈信息jstack pid | grep  'nid'  -C5  –color

jstack那些事(一)-鸿蒙开发者社区

 可以看到我们已经找到了nid为0x42的堆栈信息。


比分直播 我们查询到占用cpu最高的是 GC 线程。那我们怀疑是由于GC 频率过高导致的。

然后使用 jstat 每隔1s查看内存使用情况

jstat -gcutil pid 1000    

 

发现新生代每隔两秒就基本占满了  ,  然后查看每个key对应的数据大小,发现每个value大小是500k左右 。   

解决方式也比较简单 : 

 ○减少缓存数据大小 
 ○适当调整新生代的大小
 ○首次查询全量, 后续通过websocket推送增量给前端 

标签
已于2022-6-27 17:19:16修改
收藏
回复
举报
回复
    相关推荐