JMX堆栈分析
线程堆栈:
线程堆栈也称线程调用堆栈,是虚拟机中线程(包括锁)状态的一个瞬间快照,即系统在某一个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况。虽然不同的虚拟机打印出来的格式有些不同,但是线程堆栈的信息都包含:
- 线程名字,id,线程的数量等。
- 线程的运行状态,锁的状态(锁被哪个线程持有,哪个线程在等待锁等)
- 调用堆栈(即函数的调用层次关系)调用堆栈包含完整的类名,所执行的方法,源代码的行数。
借助堆栈信息可以帮助分析很多问题,如线程死锁,锁争用,死循环,识别耗时操作等等。在多线程场合下的稳定性问题分析和性能问题分析,线程堆栈分析湿最有效的方法,在多数情况下,无需对系统了解就可以进行相应的分析。
由于线程堆栈是系统某个时刻的线程运行状况(即瞬间快照),对于历史痕迹无法追踪。只能结合日志分析。总的来说线程堆栈是多线程类应用程序非功能型问题定位的最有效手段,最善于分析如下类型问题:
- 系统无缘无故的cpu过高
- 系统挂起,无响应
- 系统运行越来越慢
- 性能瓶颈(如无法充分利用cpu等)
- 线程死锁,死循环等
- 由于线程数量太多导致的内存溢出(如无法创建线程等)
死锁:
一个 web 服务器使用几十到几百个线程来处理大量并发用户,如果一个或多个线程使用相同的资源,线程之间的竞争就不可避免了,并且可能会发生死锁。
死锁是线程竞争的一个特殊状态,一个或是多个线程在等待其他线程完成它们的任务。
线程状态:
- NEW:线程刚被创建,但是还没有被处理。
- RUNNABLE:当调用thread.start()后,线程变成为Runnable状态。只要得到CPU,就可以执行
- Running:线程正在执行
- BLOCKED:该线程正在等待另外的不同的线程释放锁,阻塞状态
- WAITING:该线程正在等待,通过使用了 wait, join 或者是 park 方法
- TIMED_WAITING:该线程正在等待,通过使用了 sleep, wait, join 或者是 park 方法,与Waiting的区别在于Timed_Waiting的等待有时间限制
- Dead:线程执行完毕,或者抛出了未捕获的异常之后,会进入dead状态,表示该线程结束
输出堆栈:
通常将堆栈信息重定向到一个文件中,
命令 : $jstack [option] pid >> 文件
堆栈解读:
只需要关注java用户线程,其他由虚拟机自动创建的,在实际分析中可以暂时忽略:
一般来讲,我们只需要关注处于RUNNABLE状态的线程并且包含com.xxx的一行(从下往上看)
"http-nio-40243-exec-9" #125 daemon prio=5 os_prio=0 tid=0x00007fd234d9f800 nid=0x7114 runnable [0x00007fd194e87000]
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 okio.Okio$2.read(Okio.java:139)
at okio.AsyncTimeout$2.read(AsyncTimeout.java:237)
at okio.RealBufferedSource.indexOf(RealBufferedSource.java:345)
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:217)
at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:211)
at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:189)
at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:75)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
xxxxxxxxxxxxxxxx
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
at com.sun.proxy.$Proxy176.getCampaignShopCouponLimitRule(Unknown Source)
at com.yazuo.kbpos.scan.dinner.service.impl.CouponsServiceImpl.queryCouponLimitRuleList(CouponsServiceImpl.java:478)
at com.yazuo.kbpos.scan.dinner.controller.CouponsController.queryCouponLimitRuleList(CouponsController.java:115)
at com.yazuo.kbpos.scan.dinner.controller.CouponsController$$FastClassBySpringCGLIB$$95e09cc2.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738
线程解读:
- 线程名,“http-nio-40243-exec-10”
- 线程属性(如果是Daemon线程,会有Daemon标识,否则,什么都没有)
- 线程优先级,prio
- java线程对应的本地线程的优先级os_prio
- java线程标识tid
- java线程对应的本地线程标识nid
- 线程状态(运行中、等待等)
- 线程的栈信息
- 线程锁信息
线程堆栈里面的最直观的信息是当前线程的调用上下文,即从哪个函数调用到哪个函数(从下往上看),正执行到哪一类的哪一行,借助这些信息,我们就对当前系统正在做什么一目了然。

示例中基本可以定位到出现问题的方法为getCampaignShopCouponLimitRule
BLOCKED状态示例:
折叠源码
"http-nio-40243-exec-393" #1212 daemon prio=5 os_prio=0 tid=0x00007f4fdc3d9000 nid=0xe30 runnable [0x00007f4fc09d0000]
java.lang.Thread.State: RUNNABLE
at java.lang.Throwable.getStackTraceElement(Native Method)
at java.lang.Throwable.getOurStackTrace(Throwable.java:827)
- locked <0x00000000faf1f990> (a java.lang.Throwable)
at java.lang.Throwable.getStackTrace(Throwable.java:816)
at sun.reflect.GeneratedMethodAccessor2.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.log4j.spi.LocationInfo.<init>(LocationInfo.java:139)
at org.apache.log4j.spi.LoggingEvent.getLocationInformation(LoggingEvent.java:253)
at org.apache.log4j.pattern.LineLocationPatternConverter.format(LineLocationPatternConverter.java:58)
at org.apache.log4j.pattern.BridgePatternConverter.format(BridgePatternConverter.java:119)
at org.apache.log4j.EnhancedPatternLayout.format(EnhancedPatternLayout.java:546)
at org.apache.log4j.WriterAppender.subAppend(WriterAppender.java:310)
at org.apache.log4j.WriterAppender.append(WriterAppender.java:162)
at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
- locked <0x00000000e00a2d40> (a org.apache.log4j.ConsoleAppender)
at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)
at org.apache.log4j.Category.callAppenders(Category.java:206)
- locked <0x00000000e009adf0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:391)
at org.apache.log4j.Category.log(Category.java:856)
XXXXXXXXXXXXXX
"http-nio-40243-exec-392" #1211 daemon prio=5 os_prio=0 tid=0x00007f4fd8095000 nid=0xe2f waiting for monitor entry [0x00007f4fc0dd4000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:204)
- waiting to lock <0x00000000e009adf0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:391)
at org.apache.log4j.Category.log(Category.java:856)
at org.slf4j.impl.Log4jLoggerAdapter.debug(Log4jLoggerAdapter.java:230)
at com.navercorp.pinpoint.profiler.logging.Slf4jPLoggerAdapter.debug(Slf4jPLoggerAdapter.java:285)
at com.navercorp.pinpoint.plugin.redis.interceptor.ProtocolSendCommandAndReadMethodInterceptor.before(ProtocolSendCommandAndReadMethodIntercept
or.java:71)
上面的示例中,http-nio-40243-exec-393线程占用了<0x00000000e009adf0>锁,然而http-nio-40243-exec-392正在等待获取锁。
- 当一个线程占有一个锁的时候,线程堆栈会打印一个-locked
- 当一个线程正在等在其他线程释放该锁,线程堆栈会打印一个-waiting to lock
- 当一个线程占有一个锁,但又执行在该锁的wait上,线程堆栈中首先打印locked,然后打印-waiting on
一般情况下,当一个或一些线程正在等待一个锁的时候,应该有一个线程占用了这个锁,即如果有一个线程正在等待一个锁,该锁必然被另一个线程占用,从线程堆栈中看,如果看到waiting to lock<0x22bffb60>,应该也应该有locked<0x22bffb60>,大多数情况下确实如此,但是有些情况下,会发现线程堆栈中可能根本没有locked<0x22bffb60>,而只有waiting to ,这是什么原因呢,实际上,在一个线程释放锁和另一个线程被唤醒之间有一个时间窗,如果这个期间,恰好打印堆栈信息,那么只会找到waiting to ,但是找不到locked 该锁的线程,当然不同的JAVA虚拟机有不同的实现策略,不一定会立刻响应请求,也许会等待正在执行的线程执行完成。
现实场景中,一般遇到的都是两类问题:
1、CPU高,程序响应慢
1)TOP命令获取占用CPU最高的程序PID

2)找到此进程中消耗CPU较高的线程ID
# top -Hp 2093

在上图TIME+列,表示为消耗CPU时间。如图得出消耗时间较长的线程ID为:2163 2094
3)将线程ID转换为16进制
# printf "%x\n" 2163

4)使用jstack打印堆栈信息
$ jstack -l 2093 >2093.log
5)结果中查询

情况一:直接打印出代码类名,这种情况就很好定位是代码的问题,优化代码即可
情况二:"C2 CompilerThread*"开头的堆栈信息,此信息表示java编译的线程,说明java编译器编译过于频繁,tomcat程序则加上参数 -XX:CICompilerCount=4 此设置表示改变编译器线程为4线程并行处理
情况三:"catalina-exec-***"开头的堆栈信息,此信息表示程序正常处理的线程,则表示程序本身有待优化
基本思路只需要关注处于RUNNABLE状态的线程并且包含com.xxx的一行即可(从下往上看)
2、CPU正常,但是系统性能比较差,同时可能繁忙线程高
此时需要多次dump,然后找出 BLOCKED 状态的线程列表。
总结:
如果说系统慢,那么要特别关注Blocked,Waiting on condition
如果说系统的cpu耗的高,那么肯定是线程执行有死循环,那么此时要关注下Runable状态。
备注:
1.处于RUNNABLE状态的线程不一定会消耗cpu,像socket IO操作,线程正在从网络上读取数据,尽管线程状态RUNNABLE,但实际上网络io,线程绝大多数时间是被挂起的,只有当数据到达后,线程才会被唤起,挂起发生在本地代码(native)中,虚拟机无法知道真正的线程状态,因此一概显示为RUNNABLE。如果是纯java运算代码,则消耗cpu,如果网络io,很少消耗cpu。
2.处于timed_waiting,waiting状态的线程一定不消耗cpu.
JMX堆栈分析的更多相关文章
- GDB调试32位汇编堆栈分析
GDB调试32位汇编堆栈分析 测试源代码 #include <stdio.h> int g(int x){ return x+5; } int f(int x){ return g(x)+ ...
- 20145318 GDB调试汇编堆栈分析
20145318 GDB调试汇编堆栈分析 代码 #include<stdio.h> short addend1 = 1; static int addend2 = 2; const sta ...
- 使用Tcmalloc进行堆栈分析
在前一篇译文<使用TCmalloc的堆栈检查>,介绍了Tcmalloc进行堆栈检查,今天翻译<heap-profiling using tcmalloc>,了解如何 TCmal ...
- 20145219 gdb调试汇编堆栈分析
20145219 gdb调试汇编堆栈分析 代码gdbdemo.c int g(int x) { return x+19; } int f(int x) { return g(x); } int mai ...
- 20145314郑凯杰《信息安全系统设计基础》GDB调试32位汇编堆栈分析
20145314郑凯杰<信息安全系统设计基础>GDB调试32位汇编堆栈分析 本篇博客将对第五周博客中的GDB调试32位汇编堆栈进行分析 首先放上以前环境配置的图: 图1: 测试代码: #i ...
- gdb运行时结合汇编堆栈分析
一.从源代码文件到可执行文件 从C文件到可执行文件,一般来说需要两步,先将每个C文件编译成.o文件,再把多个.o文件和链接库一起链接成可执行文件.但具体来说,其实是分为四步,下面以ex ...
- 【转】java线上程序排错经验2 - 线程堆栈分析
前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...
- 20145310 GDB调试汇编堆栈分析
GDB调试汇编堆栈分析 由于老师说要逐条分析汇编代码,所以我学习卢肖明同学的方法,重新写了一篇博客. 代码: #include<stdio.h> short addend1 = 1; st ...
- Java线程堆栈分析
不知觉间工作已有一年了,闲下来的时候总会思考下,作为一名Java程序员,不能一直停留在开发业务使用框架上面.老话说得好,机会是留给有准备的人的,因此,开始计划看一些Java底层一点的东西,尝试开始在学 ...
随机推荐
- create genil component
https://help.sap.com/viewer/79ef8b1636dd492d8fd430d2d309b90f/MDG92_SP01/en-US/16a5115454744a18e10000 ...
- Jquery书写AJAX动态向页面form传数据,清空之前的数据
三种方式: 直接代码: 1.$("#mytable tr:gt(0)").remove(); 2.$("#mytable tr:not(:first)").em ...
- Java 基础 接口和多态
接口 接口的概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”. 接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成.这样将功能的 ...
- [LeetCode] 603. Consecutive Available Seats_Easy tag: SQL
Several friends at a cinema ticket office would like to reserve consecutive available seats.Can you ...
- 解放你生产力的10款效率工具,赶紧mark起来
聪明的人总是善用工具 社会的快度发展赋予我们的一个幸运之处在于:你可以通过读书或使用工具去改善自身的处境甚至是阶层. 在父母那个时代,通过熟练使用办公软件,可以使一个在水泥地做帮工的青年走进格子间.而 ...
- js动态规划---背包问题
//每种物品仅有一件,可以选择放或不放 //即f[i][w]表示前i件物品恰放入一个容量为w的背包可以获得的最大价值. //则其状态转移方程便是:f[i][w]=max{f[i-1][w],f[i-1 ...
- 最新版 INSPINIA IN+ - WebApp Admin Theme v2.7.1,包含asp.net MVC5示例代码,做管理系统最佳的选择。
下载地址:http://download.csdn.net/download/wulang1988/10039402 最新版 INSPINIA IN+ - WebApp Admin Theme v2. ...
- 1021 Deepest Root
这道题的关键在于如何求两个最远的结点,二叉树比较容易一直DFS就能找到,但是普通树就比较麻烦.要先找到一端,再去找另外一端,两端的并集就是答案.因为结点都是对称的,所以两端都是答案.还要注意去重,12 ...
- iOS 开发笔记-NSURLConnection的使用
通过NSURLConnection发送一个HTTP GET请求 //send a GET request to server with some params -(void)httpGetWithPa ...
- eclipse的调试模式以及断点运行
先在代码序号处打个点. 点击debug调试模式 然后再点击 如果有打断点,则会在断点处结束,如果没有,会一直一步一步向下直至结束.