线程转储分析

一、线程状态

在具体分析线程转储数据之前,我们首先要明确线程的状态。java.lang.Thread.State枚举类中定义了如下几种类型:

  • NEW:线程创建尚未启动。
  • RUNNABLE:包括操作系统线程状态中的Ready和Running,可能在等待时间片或者正在执行。
  • BLOCKED:线程被阻塞。
  • WAITING:不会分配CPU执行时间,直到别的线程显式的唤醒,否则无限期等待。LockSupport.park(),没有设置Timeout参数的Object.wait()和Thread.join(),会导致此现象。
  • TIMED_WAITING:不会分配CPU执行时间,直到系统自动唤醒,不需要别的线程显示唤醒。Thread.sleep(),LockSupport.parkNanos(),LockSupport.parkUntil(),设置了超时时间的Object.wait()和Thread.join(),会让线程进入有限期等待。
  • TERMINATED:线程执行结束

很多人分不清楚阻塞等待的区别,导致在分析线程转储时出现偏差。

阻塞状态与等待状态的区别是:阻塞状态的线程是在等待一个排它锁,直到别的线程释放该排它锁,该线程获取到该锁才能退出阻塞状态;

而等待状态的线程则是等待一段时间,由系统唤醒或者别的线程唤醒,该线程便退出等待状态。

线程状态转化见下图:

在任意一个时刻,线程只能处于其中的一种状态。

上面的状态颗粒度比较大,stackoverflow上有个哥们将状态定义为如下11种。在我们强制dump出来的转储中,更多的是表现为如下状态中的一种。至于这11种状态的是否权威,不敢妄下定论。

  1. NEW: Just starting up, i.e., in process of being initialized.
  2. NEW_TRANS: Corresponding transition state (not used, included for completness).
  3. IN_NATIVE: Running in native code.
  4. IN_NATIVE_TRANS: Corresponding transition state.
  5. IN_VM: Running in VM.
  6. IN_VM_TRANS: Corresponding transition state.
  7. IN_JAVA: Running in Java or in stub code.
  8. IN_JAVA_TRANS: Corresponding transition state (not used, included for completness).
  9. BLOCKED: Blocked in vm.
  10. BLOCKED_TRANS: Corresponding transition state.
  11. UNINITIALIZED

Java的线程转储指的是JVM中在某一个给定的时刻运行的所有线程的快照。一个线程转储可能包含一个单独的线程或者多个线程。在多线程环境中,如Java EE应用服务器,将会有许多线程和线程组。转储中的每一个线程都有一自己独立的逻辑,这些逻辑信息将会在堆栈信息中体现。

线程转储可以通过如下的方式生成:

  1. Unix:kill -3 ,输出到了/proc//fd/1
  2. Windows:CTRL+BREAK
  3. jstack:jstack >> 输出文件

在Linux上,大家可能都碰见过这样的问题:

就是用jstack 是拿不到thread dump的,必须加-F来强制dump。上面的问题是其实很简单,就是pid路径问题导致的,Google一下就清楚了。我想说的是,加-F dump出来的内容是不能用TDA分析的。如果不是真正的进程挂起,如果又想使用TDA进行分析的话,可以使用第一种方式进行dump。但有时候,的确必须要使用-F才能获得dump信息,比如程序挂起了。由于jstack -F/m就会使用到SA(The Serviceability Agent),我们平常不加-F/-m的时候默认走的是Instrumentationattach操作,知道这点非常重要。

二、转储说明

  线程转储生成好之后,我们便可以开始着手分析转储信息了,下面会给出几个例子逐一分析。

例子1

  • http-bio-8080-exec-1-SendThread(192.168.161.36:2181):线程的名字
  • daemon:守护线程
  • prio=10:线程的优先级(默认是5)
  • tid:Java的线程Id(线程在当前虚拟机中的唯一标识)
  • nid:线程本地标识
  • runnable:线程的状态
  • [0x00007f79795ac000]:当前运行的线程在堆中的地址范围

这个线程转储的剩余部分是调用堆栈,这个线程(http-bio-8080-exec-1-SendThread(192.168.161.36:2181))是操作系统守护线程,当前正在执行一个本地方法epollWait。该native线程正处于RUNNABLE的状态。

例子2

该转储表示守护线程http-bio-8080-exec-1会一直等待,直到ThreadPoolExecutor上有需要执行的任务分配给该线程执行(LockSupport.unpark),它才能从LockSupport.park中解脱出来。

例子3

  线程RMI Scheduler不会分配CPU执行时间,直到系统自动唤醒,不需要别的线程显示唤醒。它由于调用了LockSupport.parkNanos()进入了有限期等待。

三、案例分析

1、CPU负载高

Java进程CPU占用率居高不下,导致系统吞吐率下降,这种问题对很多初级码农来说简直就是梦魇。如果想快速的定位CPU高的原因,线程转储绝对是第一选择。CPU过高一般原因分为CPU密集型和死循环,这两种情况我们分开来分析。

死循环分析非常简单,步骤如下:

  1. 在CPU占用率高的时候,通过top -H或者ps H -eo user,pid,ppid,tid,time,%cpu,cmd –sort=%cpu将CPU占用率高的线程的tid给找出来,并即时线程转储。
  2. 将上面的找到的tid进行转化为16进制,然后在线程转储中查找对应的线程号,都会找到对应的nid标示。
  3. 定位之后,通过转储中的精确的行号,可以定位到代码中相应的位置,便可以迅速确定原因。

死循环一般是逻辑错误导致的,我们在代码中应该慎用自旋。在一些带有状态机逻辑的代码中,加入次数限制,防止程序跑飞。

CPU密集型分析跟死循环不一样,因为你通过查看线程的CPU使用率,你会发现每个线程都不是很高,但是多个加起来之后就不容乐观。这类问题,步骤如下:

  1. 在CPU占用率高的时候,通过top -H或者ps H -eo user,pid,ppid,tid,time,%cpu,cmd –sort=%cpu将CPU占用率高的前10个线程的tid给找出来,并即时线程转储。
  2. 将上面的找到的tid进行转化为16进制,然后在线程转储中查找对应的线程号,将能够找到的部分标记出来。
  3. 然后在标记出来的线程转储中寻找共性。如果通过所有的线程转储,发现线程正在执行同一个方法(同样的行号),几乎就可以确定这就是罪魁祸首了。那就可以查看代码,来做代码级别的分析了,问题便迎刃而解了。

CPU密集型,一般原因是算法效率低导致的。例如,正则表达式写的差,该用Map或者Set结构的却用了List来遍历。

线程占用CPU的类型可以分为如下两种:

us:用户空间占用CPU百分比

sy:内核空间占用CPU百分比

在linux下可以通过top命令查看详细,示例如下:

CPU us高的原因主要是执行线程不需要任何挂起动作,且一直执行,导致CPU没有机会去调度执行其他的线程,我们上面分析的都属于这类情况。

CPU sy高的原因主要是线程的运行状态要经常切换,对于这种情况,常见的一种优化方法是减少线程数。

2、响应慢(低负载)

 相应时间长,一般出现在高负载的机器上。如果在CPU的占用率很低,只有几个线程在消耗CPU的时间片,然而应用的响应时间却很长,我们首先要想到的是IO操作出现问题,至于到底是网络IO还是本地IO,我们就可以通过线程转储来定位。定位到位置之后,可以使用缓存或者减少IO操作来提升系统响应速度。

3、应用/服务宕机

当一个应用活着却不能完成任何响应的时候,表明该服务已经宕机。

此时分析线程转储,会发现大量的线程在同一个操作中罢工了,没有任何一个线程能够完成自己的操作,从而导致该JVM再没有可用的线程。很多时候都是由于死锁导致的,当一个线程持有一个对象锁而不释放,而别的线程都在等待该锁的时候便会发生死锁现象。幸运的是JVM通常会检测死锁,通过工具分析转储能够更快的定位到原因。如果想避免复杂的死锁,我们需要尽可能减小同步块的大小,并且在进行资源的访问时设置合理的超时时间,避免死等现象。

时间: 2024-08-05 19:35:51

线程转储分析的相关文章

Java 线程转储

软件维护是一个枯燥而又有挑战性的工作.只要软件功能符合预期,那么这个工作就是好的.设想一个这样的情景,你的电话半夜也一直在响(这不是一个令人愉快的感受,是吧?)任何软件系统,无论它当初是被设计的多好,也无论它经历了怎样的质量测试,仍然是有可能出现运行时性能问题.原因可能是内部功能限制或者外部环境影响.软件系统是在某种假定的情景和先入为主的观念之上被建立的.然而,当他们实际运行时,这些假定的情况可能是错误的,由此就会引起系统故障.企业的J2EE系统通常拥有庞大的用户基数,并且涉及多种系统间的交互,

使用线程转储研究运行时的应用程序

性能分析工具运行程序需要在jvm调试模式下启动,这对实际已经在用的生产应用并不适合: 还好,可以让JVM产生一个完全的线程转储,它可以显示所有线程的状态和调用堆栈: Unix系统上了可以执行kill -3 <PID>来得到: Windows系统按组合键Ctrl+Break: 执行命令并不会杀掉java进程: 这里以window为例,一按快捷键,刷刷刷: more: F:\360\workspacemy\CovertJava\srcbin>java covertjava.chat.Chat

Java线程Dump分析工具--jstack

jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式:      jstack [-l][F] pid      如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题.另外,jstack工具还可

MyCat线程模型分析

参考MyCat权威指南,对MyCat-Server里面的线程模型做简要分析: 1. 线程模型图 根据MyCat权威指南,做出以下线程模型图: MyCat的线程模型主要分为三部分(: 网络通讯线程.业务线程和定时任务线程,下面分别介绍这些线程的使用: [温馨提示] 这里排除JVM自身使用的线程,只关注MyCat服务所使用的线程,如果需要详细了解MyCat里面使用的所有线程,请参考<MyCat权威指南>-> 开发篇 -> MyCat线程模型分析 2. 网络通讯线程 MyCat-Serv

Java 死锁诊断 -- 线程转储

java线程转储 java的线程转储可以被定义为JVM中在某一个给定的时刻运行的所有线程的快照.一个线程转储可能包含一个单独的线程或者多个线程.在多线程环境中,比如J2EE应用服务器,将会有许多线程和线程组.每一个线程都有它自己的调用堆栈,在一个给定时刻,表现为一个独立功能.线程转储将会提供JVM中所有线程的堆栈信息,对于特定的线程也会给出更多信息. java虚拟机进程和java线程 java虚拟机,或者称为JVM,是一个操作系统级别的进程.java线程是JVM进程的子进程或者轻量级进程(Sol

Spring中如何获取request的方法汇总及其线程安全性分析

前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性.下面话不多说了,来一起看看详细的介绍吧. 概述 在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端ip地址.请求的url.header中的属性(如cookie.授权信息).body中的数据等.由于在Spring MVC中,处理请求的Controller.Service等对象都是单例的,因此获取request对象时最需要注意的问题,便是

Spring 中获取 request 的几种方法,及其线程安全性分析

概述在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端ip地址.请求的url.header中的属性(如cookie.授权信息).body中的数据等.由于在Spring MVC中,处理请求的Controller.Service等对象都是单例的,因此获取request对象时最需要注意的问题,便是request对象是否是线程安全的:当有大量并发请求时,能否保证不同请求/线程中使用不同的request对象.这里还有一个问题需要注意:前面所说的"在处理请

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

前言 在线上的程序中,我们可能经常会碰到程序卡死或者执行很慢的情况,这时候我们希望知道是代码哪里的问题,我们或许迫切希望得到代码运行到哪里了,是哪一步很慢,是否是进入了死循环,或者是否哪一段代码有问题导致程序很慢,或者出现了线程不安全的情况,或者是某些连接数或者打开文件数太多等问题,总之我们想知道程序卡在哪里了,哪块占用了大量的资源. 此时,或许通过线程堆栈的分析就能定位出问题. 如果能深入掌握堆栈分析的技术,很多问题都能迎刃而解,但是线程堆栈分析并不简单,设计到线上的排错问题,需要有一定的知识

jstack和线程dump分析

一:jstack jstack命令的语法格式: jstack  <pid>.可以用jps查看java进程id.这里要注意的是:1. 不同的 JAVA虚机的线程 DUMP的创建方法和文件格式是不一样的,不同的 JVM版本, dump信息也有差别.本文中,只以 SUN的 hotspot JVM 5.0_06 为例.2. 在实际运行中,往往一次 dump的信息,还不足以确认问题.建议产生三次 dump信息,如果每次 dump都指向同一个问题,我们才确定问题的典型性.  二:线程分析 2.1. JVM