您的位置:首页 > 编程语言 > Java开发

怎样分析 JAVA 的 Thread Dumps

2016-04-14 13:53 555 查看

怎样分析 JAVA 的 Thread Dumps

注: 该文章的原文是由 Tae Jin Gu 编写,原文地址为 How to Analyze JavaThread Dumps当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期慢的时候,我们需要使用 
thread dumps
。如果对于你来说,
thread
dumps
 是非常复杂的,这篇文章或许能对你有所帮助。在这里我将解释在 JAVA 中什么是 
threads
,他们的类型,怎么被创建的,怎样管理它们,你怎样从正在运行的应用中 
dump
threads
,最后你可以怎样分析它以及确定瓶颈或者是阻塞线程。本文来自于 JAVA 应用程序长期调试经验的结果。

Java and Thread

一个 web 服务器使用几十到几百个线程来处理大量并发用户,如果一个或多个线程使用相同的资源,线程之间的竞争就不可避免了,并且有时候可能会发生死锁。Thread contention 是一个线程等待锁的一个状态,这个锁被另外一个线程持有,等待被释放,不同的线程频繁访问 WEB 应用的共享资源。例如,记录一条日志,线程尝试记录日志之前必须先获取锁来访问共享资源。死锁是线程竞争的一个特殊状态,一个或是多个线程在等待其他线程完成它们的任务为了完成它们自己的任务。线程竞争会引起各种不同的问题,为了分析这些这些问题,你需要使用 dump threads
dump threads
 能给你提供每个线程的精确状态信息。

JAVA 线程的背景资料

线程同步

一个线程可以与其他线程在同一时间内被处理。为了确保一致性,当多个线程试图使用共享资源的时候,通过使用 
hread synchronization
 在同一时间内,应该只有一个线程能访问共享资源JAVA 中的线程同步可以使用监视器,每个 JAVA 对象都有一个单独的监视器,这个监视器仅仅只能被一个线程拥有,对于拥有一个由不同的线程所拥有的监视器的线程,确实需要在队列中等待,以便其他线程释放它的监视器。

线程状态

为了分析一个 
thread dump
 文件,你需要知道线程状态。线程情况在 
java.lang.Thread.State
 中阐明了。图1:线程状态NEW:线程刚被创建,但是还没有被处理。RUNNABLE:线程占用了 CPU 并且处理了一个任务。(或是是在等待状态由于操作系统的资源分配)BLOCKED:该线程正在等待另外的不同的线程释放锁,以便获取监视器锁WAITING:该线程正在等待,通过使用了 wait, join 或者是 park 方法TIMED_WAITING:该线程正在等待,通过使用了 sleep, wait, join 或者是 park 方法。(这个与 
WAITING
 不同是通过方法参数指定了最大等待时间,
WAITING
 可以通过时间或者是外部的变化解除)

线程类型

JAVA 的线程类型分为以下两种:daemon threads;非 daemon threads。Daemon threads 将停止工作当没有其他任何非 
Daemon threads
 时。即使你不创建任何线程,JAVA 应用也将默认创建几个线程。他们大部分是 
daemon
threads
。主要用于任务处理比如内存回收或者是 
JMX
。一个运行 
static void main(String[] args)
 方法的线程被作为非 
daemon
threads
 线程创建,并且当该线程停止工作的时候,所有任何其他 
daemon threads
 也将停止工作。(这个运行在 main 方法中的线程被称为 VMthread in HotSpot VM

获取一个 Thread Dump

我们将介绍三种最常用的方法,记住,有非常多的其他方法可以获取
thread dump
,一个 
thread
dump
 仅仅只能在测量的时候显示线程状态。因此为了看得线程状态的变化,建议每隔5秒提取5到10次的记录。

使用 jstack 获取 Thread Dump

在 JDK1.6 或者是更高的版本中,通过使用 jstack, 在 MS Windows 平台上可能可以获取到 
Thread Dump
。通过使用 
jps
 检查当前正在运行的JAVA进程的 PID。
<span class="matrix" style="margin: 0px; padding: 0px;">[user@linux ~]</span>$ jps -v
使用明确的 PID 作为 
jstack
 的参数来获取 
thread
dumps
[ruby] viewplain copy[user<span class="variable" style="color:teal">@linux</span> ~]<span class="variable" style="color:teal">$ </span>jstack -f <span class="number" style="color:#009999">5824</span>  

使用 jVisualVM 生成 Thread Dump

通过使用一个程序 
jVisualVM
 来生成 
Thread
Dump
如上图在左侧的任务表示当前正在运行的进程列表,点击你想要信息的那个线程,然后选择 
thread tab
 页来检查实时的线程信息。点击右边的 
Thread
Dump
 按钮来获取 
thread dump
 文件。

在 Linux 控制台生成

通过使用 
ps -ef
 命令来获取当前正在运行的 JAVA 应用程序的进程 ID。
[user@linux ~]$ ps - ef | grep java
使用精确的 pid 作为 
kill –SIGQUIT(3)
 的参数来获取 
thread
dump

Thread Dump 文件的 线程信息

线程名字:当使用 
Java.lang.Thread
 类生成一个线程的时候,该线程将被命名为 
Thread-(Number)
。但是当使用
java.util.concurrent.ThreadFactory
 类的时候,它将被命名为 
pool-(number)-thread-(number)
。优先级:代表该线程的优先级线程 ID:代表该线程的唯一 ID,(一些有用的信息,比如该线程的 CPU 使用率或者是内存使用率,都能通过该线程 ID 获取到)。线程状态:代表该线程当前的状态线程调用栈:代表该线程的调用栈信息

Thread Dump Patterns by Type When Unable to Obtain a Lock (BLOCKED)

这个应用程序的整体性能下降是因为一个线程占用了锁阻止了其他线程获得锁,在下面的示例中,
BLOCKED_TEST pool-1-thread-1
 线程占用了 
<0x0000000780a000b0>
 锁,然而 
BLOCKED_TEST
pool-1-thread-2
 和 
BLOCKED_TEST pool-1-thread-3 threads
 正在等待获取锁。

当在死锁状态

这是当线程 A 需要获取线程 B 的锁来继续它的任务,然而线程 B 也需要获取线程 A 的锁来继续它的任务的时候发生的。在
thread dump
 中,你能看到 
DEADLOCK_TEST-1
 线程持有 
0x00000007d58f5e48
 锁,并且尝试获取 
0x00000007d58f5e60
锁。你也能看到 
DEADLOCK_TEST-2
 线程持有 
0x00000007d58f5e60
,并且尝试获取 
0x00000007d58f5e78
,同时 
DEADLOCK_TEST-3
 线程持有 
0x00000007d58f5e78
,并且在尝试获取 
0x00000007d58f5e48
 锁,如你所见,每个线程都在等待获取另外一个线程的锁,这状态将不会被改变直到一个线程丢弃了它的锁。

当持续等待从远处服务器接收消息

该线程是正常的,因为它的状态为 RUNNABLE,尽管如此,当你按照时间顺序排列 
Thread Dump
,你会发现 
socketReadThread
 线程正在无限等待读取socket。

当 Waiting 时

线程保持在 
Waiting
 状态,在 
Thread
Dump
 中,
IoWaitThread
 线程保持等待状态来从 
LinkedBlockingQueue
 接收消息。如果 
LinkedBlockingQueue
 一直没有消息,该线程的状态将不会改变。

当线程的资源不能正常的被组织

不必要的线程会堆积起来,当线程的资源不能被正常的组织的话,如果这个发送了,建议监控线程组织过程或检查线程终止的条件。

使用 Thread Dump 怎样解决问题

示例1:当 CPU 利用率高的异常

提取获取最高 CPU 使用率的线程。获取使用 CPU 最多的轻量级进程(LWP),把它的唯一标示码 (10039) 转换成十六进制 (0x2737)。然后获取进程的 
Thread Dump
,检查进程的动作。通过 PID 10029 来提取应用程序的 
Thread Dump
,然后通过一个 nid 0x2737 来找到这个线程。每个小时的几个时间提取 
Thread Dump
,然后检查线程的状态来确定问题。

示例2:当进程的性能异常的慢

多次获得 
thread dumps
 后,找出 
BLOCKED
 状态的线程列表。在多次获取 
thread dumps
 后,取得 
BLOCKED
 状态的线程列表。如果线程是 
BLOCKED
 的,提取线程尝试获取的相关联的锁。通过 
thread dumps
,你能确定线程状态停止在 
BLOCKED
,因为锁 
<0xe0375410>
 不能被获取到,这个问题可以通过分析当前夯住的线程的 
stacktrace
 来解决。使用 
DBMS
 的时候,为什么以上的范例经常出现再应用程序中,这有两个原因。第一个原因是配置不当。尽管事实是该线程仍然在工作,它们不能展示它们最好的性能,因为 
DBCP
 的配置文件没有配置正确。如果你多次提取 
thread
dumps
 并且对比它们,你将经常看到被阻塞的线程之前处于不同的状态。第二个原因是不正常的连接。当与 
DBMS
 的连接保持在不正常的状态,线程将等待直到超时。在这个例子中,通过多次提取 
thread
dumps
 并对比它们,你会发现与 DBMS 相关的线程仍然在阻塞状态。通过适当改变一些值,比如超时时间,你可以缩短问题发生的时间。

为简单的 Thread Dump 命名线程编码

当使用 
java.lang.Thread
 对象创建线程的时候,线程被命名为 Thread-(Number) 。当使用 
java.util.concurrent.DefaultThreadFactory
 对象创建线程的时候,线程被命名为named pool-(Number)-thread-(Number)。当为应用程序分析成百上千的线程的时候,如果线程依然用它们默认的名字,分析它们将变得非常困难,因为这是非常难以辨别这些线程来分析的。因此,你被建议开发一个命名线程的规则当一个新线程被创建的时候。当你使用 
java.lang.Thread
 创建线程,你可以通过创建参数给该线程定义个约定俗成的名字。当你使用 
java.util.concurrent.ThreadFactory
 创建线程的时候,你可以通过生成你自己的线程工厂来命名它,如果你不需要特别的功能性,你可以使用 
MyThreadFactory
 作为以下描述:[java] viewplain copy<span class="keyword" style="font-weight:bold">import</span> java.util.concurrent.ConcurrentHashMap;  <span class="keyword" style="font-weight:bold">import</span> java.util.concurrent.ThreadFactory;  <span class="keyword" style="font-weight:bold">import</span> java.util.concurrent.atomic.AtomicInteger;    <span class="keyword" style="font-weight:bold">public</span> <span class="class" style="color:#445588; font-weight:bold"><span class="keyword" style="color:#333333">class</span> <span class="title" style="">MyThreadFactory</span> <span class="keyword" style="color:#333333">implements</span> <span class="title" style="">ThreadFactory</span> {</span>    <span class="keyword" style="font-weight:bold">private</span> <span class="keyword" style="font-weight:bold">static</span> <span class="keyword" style="font-weight:bold">final</span> ConcurrentHashMap<String, AtomicInteger> POOL_NUMBER =                                                         <span class="keyword" style="font-weight:bold">new</span> ConcurrentHashMap<String, AtomicInteger>();    <span class="keyword" style="font-weight:bold">private</span> <span class="keyword" style="font-weight:bold">final</span> ThreadGroup group;    <span class="keyword" style="font-weight:bold">private</span> <span class="keyword" style="font-weight:bold">final</span> AtomicInteger threadNumber = <span class="keyword" style="font-weight:bold">new</span> AtomicInteger(<span class="number" style="color:#009999">1</span>);    <span class="keyword" style="font-weight:bold">private</span> <span class="keyword" style="font-weight:bold">final</span> String namePrefix;      <span class="keyword" style="font-weight:bold">public</span> MyThreadFactory(String threadPoolName) {          <span class="keyword" style="font-weight:bold">if</span> (threadPoolName == <span class="keyword" style="font-weight:bold">null</span>) {            <span class="keyword" style="font-weight:bold">throw</span> <span class="keyword" style="font-weight:bold">new</span> NullPointerException(<span class="string" style="color:#dd1144">"threadPoolName"</span>);        }              POOL_NUMBER.putIfAbsent(threadPoolName, <span class="keyword" style="font-weight:bold">new</span> AtomicInteger());          SecurityManager securityManager = System.getSecurityManager();        group = (securityManager != <span class="keyword" style="font-weight:bold">null</span>) ? securityManager.getThreadGroup() :                                                      Thread.currentThread().getThreadGroup();          AtomicInteger poolCount = POOL_NUMBER.get(threadPoolName);          <span class="keyword" style="font-weight:bold">if</span> (poolCount == <span class="keyword" style="font-weight:bold">null</span>) {              namePrefix = threadPoolName + <span class="string" style="color:#dd1144">" pool-00-thread-"</span>;        } <span class="keyword" style="font-weight:bold">else</span> {              namePrefix = threadPoolName + <span class="string" style="color:#dd1144">" pool-"</span> + poolCount.getAndIncrement() + <span class="string" style="color:#dd1144">"-thread-"</span>;        }    }      <span class="keyword" style="font-weight:bold">public</span> Thread newThread(Runnable runnable) {        Thread thread = <span class="keyword" style="font-weight:bold">new</span> Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), <span class="number" style="color:#009999">0</span>);          <span class="keyword" style="font-weight:bold">if</span> (thread.isDaemon()) {              thread.setDaemon(<span class="keyword" style="font-weight:bold">false</span>);        }          <span class="keyword" style="font-weight:bold">if</span> (thread.getPriority() != Thread.NORM_PRIORITY) {              thread.setPriority(Thread.NORM_PRIORITY);        }          <span class="keyword" style="font-weight:bold">return</span> thread;    }  }  

使用 MBean 获取更多的细节信息

你可以使用 MBean 来获取 
ThreadInfo
 对象。你也可以获取更加多通过 thread dumps 不能获取的信息。通过使用 
ThreadInfo
。你可以使用方法 
ThreadInfo
 来提取阻塞线程或者是等待线程花费的时间。并利用这一点,你也可以得到那些处于非活动状态的时间异常长的线程列表。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: