怎样分析 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来提取阻塞线程或者是等待线程花费的时间。并利用这一点,你也可以得到那些处于非活动状态的时间异常长的线程列表。
相关文章推荐
- java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView
- JAVA使用FTPClient类读写FTP
- JAVA的一些相关记录
- Eclipse下使用Android Design Support Library中的控件(比如TabLayout)
- windows上安装myeclipse2014,链接+破解地址
- eclipse无法部署程序到android设备中
- LeetCode 141 -Linked List Cycle ( JAVA )
- 用Collections.sort方法对list排序
- android studio 把jdk版本设置为1.7
- 浅析Java中的final关键字
- Java - 文档注释
- JAVA之登录页面记住密码之COOKIE实现
- spring mvc poi导出excel
- Java使用Jacob转换Word为HTML
- window下在同一台机器上安装多个版本jdk,修改环境变量不生效问题处理办法
- MyEclipse 常规设置
- spring参数传递的问题
- 【转载】深入理解Java的接口和抽象类
- JAVA Thread Dump 分析综述
- Java Web基础知识之Servlet(1):初识Servlet