1. 背景
在tomcat运行的过程中,由于开发人员不小心写的死循环代码,影响系统的稳定,导致系统停止响应,部分维护人员经验不足,无法定位问题的根源,常常用重启服务的方式来应急,但该方法治标不治本,运行一段时间后,仍然会出现上述问题,影响用户体验。
本文利用java虚拟机自带的工具jstack命令,对问题代码进行定位与分析。
2. jstack简介
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
3. 操作步骤
- (1)查看问题进程
在Linux系统的ssh中输入命令行中输入top,查看CPU占用情况 :
top
记录资源较高的java进程号ID为21502
- (2)查找对应线程
命令行中输入如下信息,查看该进程对应的线程占用资源情况:
top -Hp 21502
找到资源占用最高的线程ID为21694,并将其换算为十六进制为54BE,一会用到。
- (3)导出信息
命令行中输入如下命令,将该进程信息导出到test.txt文件中
jstack 21502 >test.txt
并将生成的文件下载到本地。(此段代码可能会报错,如果报错请查看 第三章 常见问题)
- (4)异常定位
在test.txt文件中搜索刚才换算的十六进制54BE,定位到如下片段
"http-8888-3" daemon prio=10 tid=0x00002aacd1488800 nid=0x54be runnable [0x0000000041513000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:260)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:65)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:123)
- locked <0x00002aaace722b00> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:432)
- locked <0x00002aaace666fe0> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:202)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:272)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:85)
- locked <0x00002aaace722968> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:168)
at java.io.PrintStream.write(PrintStream.java:477)
- locked <0x00002aaace666fe0> (a java.io.PrintStream)
at java.io.PrintStream.print(PrintStream.java:547)
at java.io.PrintStream.println(PrintStream.java:686)
- locked <0x00002aaace666fe0> (a java.io.PrintStream)
at org.apache.tomcat.util.log.SystemLogHandler.println(SystemLogHandler.java:218)
at dky.Test.test(Test.java:8)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at kd.ln.common.service.BeanTool.invokeMethod(BeanTool.java:179)
at kd.ln.common.service.BeanTool.invokeMethod(BeanTool.java:142)
at kd.ln.common.service.BeanTool.invokeMethod(BeanTool.java:130)
at kd.ln.common.service.BeanTool.invokeMethod(BeanTool.java:125)
at kd.ln.common.service.CommonService.invokeMethod(CommonService.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at flex.messaging.services.remoting.adapters.JavaAdapter.invoke(JavaAdapter.java:418)
at flex.messaging.services.RemotingService.serviceMessage(RemotingService.java:183)
at flex.messaging.MessageBroker.routeMessageToService(MessageBroker.java:1400)
at flex.messaging.endpoints.AbstractEndpoint.serviceMessage(AbstractEndpoint.java:1005)
at flex.messaging.endpoints.amf.MessageBrokerFilter.invoke(MessageBrokerFilter.java:103)
at flex.messaging.endpoints.amf.LegacyFilter.invoke(LegacyFilter.java:158)
at flex.messaging.endpoints.amf.SessionFilter.invoke(SessionFilter.java:44)
at flex.messaging.endpoints.amf.BatchProcessFilter.invoke(BatchProcessFilter.java:67)
at flex.messaging.endpoints.amf.SerializationFilter.invoke(SerializationFilter.java:166)
at flex.messaging.endpoints.BaseHTTPEndpoint.service(BaseHTTPEndpoint.java:291)
at flex.messaging.MessageBrokerServlet.service(MessageBrokerServlet.java:353)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:845)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
at java.lang.Thread.run(Thread.java:619)
找到可能出现问题代码的位置,dky.Test.test(Test.java:8)
查找对应的java文件,发现存在死循环。
package dky;
public class Test {
public static void test(String ss){
int i=0;
while(true){
if(i%10000==0){
System.out.println(i);
}
i++;
}
}
public static void main(String[] args) {
test("");
}
}
4常见问题
在执行jstack命令时,可能会报如下错误
-bash: jstack: command not found
出现这种错误,一般情况是服务器没有配置环境变量,对于这种问题的解决方案为,找到jdk对应的路径,如/home/d5000/jdk/bin/,将路径加到命令行中即可。
/home/d5000/jdk/bin/./jstack 21502 >test.txt