线程堆栈:

线程堆栈也称线程调用堆栈,是虚拟机中线程(包括锁)状态的一个瞬间快照,即系统在某一个时刻所有线程的运行状态,包括每一个线程的调用堆栈,锁的持有情况。虽然不同的虚拟机打印出来的格式有些不同,但是线程堆栈的信息都包含:

  • 线程名字,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堆栈分析的更多相关文章

  1. GDB调试32位汇编堆栈分析

    GDB调试32位汇编堆栈分析 测试源代码 #include <stdio.h> int g(int x){ return x+5; } int f(int x){ return g(x)+ ...

  2. 20145318 GDB调试汇编堆栈分析

    20145318 GDB调试汇编堆栈分析 代码 #include<stdio.h> short addend1 = 1; static int addend2 = 2; const sta ...

  3. 使用Tcmalloc进行堆栈分析

    在前一篇译文<使用TCmalloc的堆栈检查>,介绍了Tcmalloc进行堆栈检查,今天翻译<heap-profiling using tcmalloc>,了解如何 TCmal ...

  4. 20145219 gdb调试汇编堆栈分析

    20145219 gdb调试汇编堆栈分析 代码gdbdemo.c int g(int x) { return x+19; } int f(int x) { return g(x); } int mai ...

  5. 20145314郑凯杰《信息安全系统设计基础》GDB调试32位汇编堆栈分析

    20145314郑凯杰<信息安全系统设计基础>GDB调试32位汇编堆栈分析 本篇博客将对第五周博客中的GDB调试32位汇编堆栈进行分析 首先放上以前环境配置的图: 图1: 测试代码: #i ...

  6. gdb运行时结合汇编堆栈分析

    一.从源代码文件到可执行文件         从C文件到可执行文件,一般来说需要两步,先将每个C文件编译成.o文件,再把多个.o文件和链接库一起链接成可执行文件.但具体来说,其实是分为四步,下面以ex ...

  7. 【转】java线上程序排错经验2 - 线程堆栈分析

    前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题 ...

  8. 20145310 GDB调试汇编堆栈分析

    GDB调试汇编堆栈分析 由于老师说要逐条分析汇编代码,所以我学习卢肖明同学的方法,重新写了一篇博客. 代码: #include<stdio.h> short addend1 = 1; st ...

  9. Java线程堆栈分析

    不知觉间工作已有一年了,闲下来的时候总会思考下,作为一名Java程序员,不能一直停留在开发业务使用框架上面.老话说得好,机会是留给有准备的人的,因此,开始计划看一些Java底层一点的东西,尝试开始在学 ...

随机推荐

  1. RN无限轮播以及ScrollView的大小调节问题

    如果你的ScrollView的大小是全屏,height不能用,这种情况需要给ScrollView添加一个容器View,然后调节容器View的大小 无限轮播这里我使用的是一个第三方的插件react-na ...

  2. jenkins的pipeline的使用

    1.安装Pipeline Maven Integration Plugin 2.新建任务 3.编写pipeline代码 node { stage('get clone') { checkout([$c ...

  3. 各版本系统安装tesseract-ocr

    Mac版本 1.tesseract-ocr安装  brew install tesseract-ocr 注意:如果未安装brew命令,可以输入命令: brew官网:http://brew.sh /us ...

  4. what's the 单例模式

    what's the 单例模式 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一个类只有一个对象实例 ...

  5. MySQL 8.0.11 报错[ERROR] [MY-011087] Different lower_case_table_names settings for server ('1')

    --报错信息: 2018-06-07T19:52:26.943083+08:00 0 [System] [MY-010116] [Server] /usr/local/mysql/bin/mysqld ...

  6. 关于PHP将对象数据写入日志的问题

    有时候在调试项目的时候,需要将一个对象或者对象的实例记录下来观察数据,如果用json_encode可能拿到的是空数据, 此时,改为使用 $data = print_r($data,1); 然后将 $d ...

  7. extjs model store学习笔记

    http://docs.sencha.com/extjs/6.2.0/guides/core_concepts/data_package.html // 定义一个ModelExt.define('My ...

  8. [LeetCode] 830. Positions of Large Groups_Easy tag: Two Pointers

    In a string S of lowercase letters, these letters form consecutive groups of the same character. For ...

  9. [LeetCode] 577. Employee Bonus_Easy tag: SQL

    Select all employee's name and bonus whose bonus is < 1000. Table:Employee +-------+--------+---- ...

  10. js模拟队列----小优先队列

    队列:先进先出,后进后出 var Queue = (function(){ var item = new WeakMap(); class Queue{ constructor(){ item.set ...