Java Developer课程程序中有很多关于JVM内部的主题。 我们了解收集,字节码,垃圾收集器等的工作机制。 今天,我们将您的注意力转移到一篇有关线程转储的相当有趣的文章的翻译上。 它是什么,如何获得以及如何使用。是否想学习如何分析线程转储? 深入了解如何在Java中获取线程转储以及以后如何使用它。
大多数现代Java应用程序都是多线程的。 多线程可以显着扩展应用程序的功能,同时又会带来极大的复杂性。
在单线程应用程序中,可以使用所有资源(共享内存,输入/输出操作等)而无需同步,因为 在任何给定时间,只有一个线程使用该资源。
对于多线程应用程序,当多个线程可以使用所有可用的(通常不止一个)核心处理器(CPU)时,有必要在使程序复杂化和可能提高性能之间找到折衷方案。 如果一切都正确完成,则使用多线程(在
阿姆达尔定律中正式化),可以显着提高应用程序性能。 但是,必须记住要同时提供多个流对共享资源的访问。 在大多数情况下,诸如Spring之类的框架封装了线程,并向用户隐藏了许多技术细节。 但是,在使用现代复杂框架的情况下,可能会出问题,并且作为用户的我们将遇到难以解决的多线程错误。
幸运的是,Java配备了一种特殊的机制,可以在任何给定的时间获取有关所有线程的当前状态的信息-这是线程转储(一种快照)。 在本文中,我们将学习如何为实际大小的应用程序获取线程转储,以及如何分析此转储。
假定读者具有有关多线程编程的基本信息,并且了解线程同步和共享资源使用的问题。 但是,刷新一些基本术语和概念并不是多余的。
基本术语
乍一看,Java线程转储可能看起来像“中文字母”,以下概念是理解它的关键。 通常,让我们重复多线程的基本术语,这些术语将用于分析转储。
- 线程或线程是由Java虚拟机(JVM)管理的离散多线程单元。 JVM线程对应于操作系统(OS)中的线程-本机线程,它们实现代码执行机制。
每个线程都有唯一的标识符和名称。 流可以是“恶魔”和“不是恶魔”。
当所有非守护进程线程终止或调用Runtime.exit方法时,程序终止。 工作的“守护程序”不会影响程序的完成。 即 JVM等待所有“非守护程序”完成并关闭;他们没有注意“非守护程序”。
有关更多信息,请参见Thread类文档。
流可能处于以下状态之一:
- 活动线程或“活动”线程-可以完成某些工作的线程(正常状态)。
- 阻塞线程或“阻塞”-试图进入同步部分(已同步)的线程,但另一个线程已经设法先进入此块,并且随后所有尝试进入同一块的线程均被阻塞。
- 等待线程或“等待”-一个线程,调用了wait方法(可能带有超时),现在正在等待另一个方法在同一对象上执行notify或nonifyAll 。
请注意,如果线程调用带有超时的等待并且该超时已过期,则该线程不被视为“等待”。 - 正在休眠的线程或“正在休眠”的线程-当前未运行的线程,因为 执行Thread.sleep方法(指示“睡眠”的持续时间)。
- Monitor是JVM使用的一种机制,用于提供对单个对象的多线程访问。 该机制使用特殊的synced关键字启动。
Java中的每个对象都有一个可以与线程同步的监视器,即 设置一个锁,以确保在释放该锁之前,没有其他线程可以访问该对象,即 线程-锁的所有者不会退出同步块。
有关更多信息,请参见Java语言规范(JLS)的“ 同步”部分(17.1) 。
- 死锁是指一个线程(例如A)阻塞了一个资源,它需要另一个被另一个线程(例如B)所阻塞的资源。流B不会释放该资源,因为 为了完成某个操作,他需要一个被线程A阻塞的资源。事实证明,线程A正在等待资源被线程B解锁,后者正在等待另一个资源被线程A解锁。因此,线程正在互相等待。 结果,整个程序挂起并等待线程以某种方式解锁并继续工作。 死锁中可能有许多线程。 这个问题被称为“用餐哲学家的问题”。

- 活锁是一种情况,线程A迫使线程B执行某些操作,这又使线程A执行初始操作,这又导致线程B进行操作。获得了循环依赖性。 可以想象这是一条狗追尾。 与死锁类似,在Livelock情况下,程序不会进展,即 不会执行有用的操作,但是,在这种情况下,不会阻塞线程。
所提供的术语在描述多线程领域时并不详尽,但这足以开始分析线程转储。
可在以下资源中找到更多详细信息:
《 JLS和
Java并发实践》 第17节使用这些关于Java中流程的简单概念,我们可以创建一个测试应用程序。 对于此应用程序,我们将编译线程转储。 我们将分析产生的转储,并提取有关当前应用程序流的有用信息。
创建一个示例程序
在创建线程转储之前,我们需要开发一个Java应用程序。 传统的“你好,世界!” 对于我们的目的而言太简单了,而应用程序的中型转储可能太复杂而无法演示。 基于此,我们将创建一个相当简单的应用程序,在其中创建两个线程。 线程陷入僵局:
public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceB, resourceA)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized(firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized(secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } }
该程序创建两个资源:resourceA和resourceB,并启动两个线程:threadLockingResourceAFirst和threadLockingResourceBFirst,这两个线程彼此阻塞。
死锁的原因是线程对资源的“交叉”阻塞。
发生死锁的原因是试图“相互”占用资源,即 threadLockingResourceAFirst线程捕获resourceA资源,threadLockingResourceBFirst线程捕获resourceB资源。 之后,threadLockingResourceAFirst在不释放资源的情况下尝试捕获resourceB,threadLockingResourceBFirst在不释放资源的情况下尝试捕获resourceA。 结果,线程被阻塞。 添加了1s延迟以保证阻塞。 线程正在等待释放必要的资源,但这永远不会发生。
程序的输出将如下所示(每次启动时java.lang.Object @之后的数字将不同):
Thread-0: locked resource -> java.lang.Object@149bc794 Thread-1: locked resource -> java.lang.Object@17c10009
这些消息输出后,程序看起来像正在运行(执行该程序的过程尚未完成),而程序则不执行任何工作。 这就是实践中的僵局。 要解决该问题,我们需要手动创建一个踩踏转储并分析线程的状态。
线程转储生成
实际上,创建线程转储时Java程序可能会崩溃。 但是,在某些情况下(例如,在出现死锁的情况下),该程序不会结束,并且不会创建线程转储,而只是挂起。 要创建此类挂起程序的转储,首先,您需要找出程序进程的标识符,即 进程ID(PID)。 为此,您可以使用JVM Process Status(JPS)实用程序,该实用程序从版本7开始,是Java Development Kit(JDK)的一部分。 要找到我们挂起的程序的进程PID,我们只需在终端(Windows或Linux)中执行jps:
$ jps 11568 DeadlockProgram 15584 Jps 15636
第一列是正在运行的Java进程的本地虚拟机的标识符(本地VM ID,即lvmid)。 在本地JVM的上下文中,lvmid指向Java进程的PID。
应当注意,该值可能与上述值不同。 第二列是应用程序的名称,它可以指向主类,jar文件的名称,或者等于“ Unknown”。 这完全取决于应用程序的启动方式。
在我们的例子中,应用程序名称DeadlockProgram是程序启动时启动的主要类的名称。 在上面的程序11568的PID的示例中,此信息足以生成线程转储。 要生成转储,我们将从版本7开始使用
jstack实用程序,它是JDK的一部分。要获取转储,我们将程序的PID
传递给
jstack并指定-l标志(创建长列表)。 该实用程序的输出将重定向到文本文件,即 thread_dump.txt:
jstack -l 11568 > thread_dump.txt
生成的thread_dump.txt文件包含已挂起程序的线程转储,并包含用于诊断死锁原因的重要信息。
如果JDK在版本7之前使用,则可以使用Linux实用程序-带有-3标志的
kill生成转储。 调用kill -3将向程序发送SIGQUIT信号。
在我们的情况下,调用将如下所示:
kill -3 11568
简单线程转储分析
打开thread_dump.txt文件,我们将看到以下内容:
2018-06-19 16:44:44
全线程转储Java HotSpot(TM)64位服务器VM(10.0.1 + 10混合模式):
线程类SMR信息:
_java_thread_list = 0x00000250e5488a00,长度= 13,元素= {
0x00000250e4979000、0x00000250e4982800、0x00000250e52f2800、0x00000250e4992800,
0x00000250e4995800、0x00000250e49a5800、0x00000250e49ae800、0x00000250e5324000,
0x00000250e54cd800、0x00000250e54cf000、0x00000250e54d1800、0x00000250e54d2000,
0x00000250e54d0800
}
“引用处理程序”#2守护程序prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28等待条件[0x000000b82a9ff000]
java.lang.Thread.State:RUNNABLE
在java.lang.ref.Reference.waitForReferencePendingList(java.base@10.0.1/Native方法)
在java.lang.ref.Reference.processPendingReferences(java.base@10.0.1/Reference.java:174)
在java.lang.ref.Reference.access $ 000(java.base@10.0.1/Reference.java:44)
在java.lang.ref.Reference $ ReferenceHandler.run(java.base@10.0.1/Reference.java:138)
锁定的可拥有同步器:
-无
“最终化器”#3守护程序prio = 8 os_prio = 1 tid = 0x00000250e4982800 nid = 0x2a54 in Object.wait()[0x000000b82aaff000]
java.lang.Thread.State:等待(在对象监视器上)
在java.lang.Object.wait(java.base@10.0.1/Native方法)
-等待<0x0000000089509410>(java.lang.ref.ReferenceQueue $ Lock)
在java.lang.ref.ReferenceQueue.remove(java.base@10.0.1/ReferenceQueue.java:151)
-等待重新锁定wait()<0x0000000089509410>(java.lang.ref.ReferenceQueue $ Lock)
在java.lang.ref.ReferenceQueue.remove(java.base@10.0.1/ReferenceQueue.java:172)
在java.lang.ref.Finalizer $ FinalizerThread.run(java.base@10.0.1/Finalizer.java:216)
锁定的可拥有同步器:
-无
“信号调度程序”#4守护程序prio = 9 os_prio = 2 tid = 0x00000250e52f2800 nid = 0x2184可运行[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
锁定的可拥有同步器:
-无
“附加侦听器”#5守护程序prio = 5 os_prio = 2 tid = 0x00000250e4992800 nid = 0x1624等待条件[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
锁定的可拥有同步器:
-无
“ C2 CompilerThread0”#6守护程序prio = 9 os_prio = 2 tid = 0x00000250e4995800 nid = 0x4198等待条件[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
没有编译任务
锁定的可拥有同步器:
-无
“ C2 CompilerThread1”#7守护程序prio = 9 os_prio = 2 tid = 0x00000250e49a5800 nid = 0x3b98等待条件[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
没有编译任务
锁定的可拥有同步器:
-无
“ C1 CompilerThread2”#8守护程序prio = 9 os_prio = 2 tid = 0x00000250e49ae800 nid = 0x1a84等待条件[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
没有编译任务
锁定的可拥有同步器:
-无
“清除线程”#9守护程序prio = 9 os_prio = 2 tid = 0x00000250e5324000 nid = 0x5f0可运行[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
锁定的可拥有同步器:
-无
“服务线程”#10守护程序prio = 9 os_prio = 0 tid = 0x00000250e54cd800 nid = 0x169c可运行[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
锁定的可拥有同步器:
-无
“ Common-Cleaner”#11守护程序prio = 8 os_prio = 1 tid = 0x00000250e54cf000 nid = 0x1610 in Object.wait()[0x000000b82b2fe000]
java.lang.Thread.State:TIMED_WAITING(在对象监视器上)
在java.lang.Object.wait(java.base@10.0.1/Native方法)
-等待<0x000000008943e600>(java.lang.ref.ReferenceQueue $ Lock)
在java.lang.ref.ReferenceQueue.remove(java.base@10.0.1/ReferenceQueue.java:151)
-等待重新锁定wait()<0x000000008943e600>(java.lang.ref.ReferenceQueue $ Lock)
在jdk.internal.ref.CleanerImpl.run(java.base@10.0.1/CleanerImpl.java:148)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
在jdk.internal.misc.InnocuousThread.run(java.base@10.0.1/InnocuousThread.java:134)
锁定的可拥有同步器:
-无
“线程-0”#12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec等待监视器条目[0x000000b82b4ff000]
java.lang.Thread.State:已阻止(在对象监视器上)
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465b0>(一个java.lang.Object)
-锁定<0x00000000894465a0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
锁定的可拥有同步器:
-无
“ Thread-1”#13 prio = 5 os_prio = 0 tid = 0x00000250e54d2000 nid = 0x415c等待监视器条目[0x000000b82b5ff000]
java.lang.Thread.State:已阻止(在对象监视器上)
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465a0>(一个java.lang.Object)
-锁定<0x00000000894465b0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
锁定的可拥有同步器:
-无
“ DestroyJavaVM”#14 prio = 5 os_prio = 0 tid = 0x00000250e54d0800 nid = 0x2b8c等待条件[0x0000000000000000]
java.lang.Thread.State:RUNNABLE
锁定的可拥有同步器:
-无
“ VM线程” os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920可运行
“ GC线程号0” os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c可运行
“ GC线程#1” os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4可运行
“ GC线程#2” os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8可运行
“ GC线程#3” os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0可运行
“ G1主标记” os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068可运行
“ G1 Conc#0” os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28可运行
“ G1优化#0” os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c可运行
“ G1优化#1” os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890可运行
“ G1优化#2” os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8可运行
“ G1优化#3” os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00可运行
“ G1 Young RemSet采样” os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4可运行
“ VM定期任务线程” os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468等待条件
JNI全球参考:2
发现了一个Java级死锁:
==============================
“线程-0”:
等待锁定监视器0x00000250e4982480(对象0x00000000894465b0,一个java.lang.Object),
由“ Thread-1”持有
“线程1”:
等待锁定监视器0x00000250e4982380(对象0x00000000894465a0,一个java.lang.Object),
由“线程-0”持有
上面列出的线程的Java堆栈信息:
==================================================== =
“线程-0”:
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465b0>(一个java.lang.Object)
-锁定<0x00000000894465a0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
“线程1”:
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465a0>(一个java.lang.Object)
-锁定<0x00000000894465b0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
发现1个死锁。
介绍性信息
尽管乍看之下该文件似乎过于复杂和混乱,但实际上,如果逐步将其分解,则相当简单。
第一行表示转储形成的时间,第二行-有关在其上接收到转储的JVM的诊断信息:
2018-06-19 16:44:44 Full thread dump Java HotSpot(TM) 64-Bit Server VM (10.0.1+10 mixed mode):
本节中没有流信息。 此处设置了收集转储的系统的一般上下文。
一般流程信息
以下部分提供有关转储收集时系统中正在运行的线程的信息:
线程类SMR信息:
_java_thread_list = 0x00000250e5488a00,长度= 13,元素= {
0x00000250e4979000、0x00000250e4982800、0x00000250e52f2800、0x00000250e4992800,
0x00000250e4995800、0x00000250e49a5800、0x00000250e49ae800、0x00000250e5324000,
0x00000250e54cd800、0x00000250e54cf000、0x00000250e54d1800、0x00000250e54d2000,
0x00000250e54d0800
}
以下部分列出:
安全内存回收(SMR)信息它包含有关JVM外部线程的信息,即 这些不是虚拟机线程或垃圾回收线程。 如果查看这些线程的地址,则会注意到它们与
tid的值相对应
-tid的值-操作系统中的“自然铁”(本机)地址,而不是线程ID。
省略号用于隐藏冗余信息:
“引用处理程序”#2 ... tid = 0x00000250e4979000 ...
“终结器”#3 ... tid = 0x00000250e4982800 ...
“信号调度器”#4 ... tid = 0x00000250e52f2800 ...
“附加监听器”#5 ... tid = 0x00000250e4992800 ...
“ C2 CompilerThread0”#6 ... tid = 0x00000250e4995800 ...
“ C2 CompilerThread1”#7 ... tid = 0x00000250e49a5800 ...
“ C1 CompilerThread2”#8 ... tid = 0x00000250e49ae800 ...
“清除线程”#9 ... tid = 0x00000250e5324000 ...
“服务线程”#10 ... tid = 0x00000250e54cd800 ...
“通用清洁器”#11 ... tid = 0x00000250e54cf000 ...
“线程-0”#12 ... tid = 0x00000250e54d1800 ...
“线程-1”#13 ... tid = 0x00000250e54d2000 ...
“ DestroyJavaVM”#14 ... tid = 0x00000250e54d0800 ...
流
在SMR块之后是线程列表。 我们列表中的第一个线程是引用处理程序:
“引用处理程序”#2守护程序prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28等待条件[0x000000b82a9ff000]
java.lang.Thread.State:RUNNABLE
在java.lang.ref.Reference.waitForReferencePendingList(java.base@10.0.1/Native方法)
在java.lang.ref.Reference.processPendingReferences(java.base@10.0.1/Reference.java:174)
在java.lang.ref.Reference.access $ 000(java.base@10.0.1/Reference.java:44)
在java.lang.ref.Reference $ ReferenceHandler.run(java.base@10.0.1/Reference.java:138)
锁定的可拥有同步器:
-无
流的简短描述
每个线程的第一行提供了一般说明。 说明包含以下各项:
部分 | 例子 | 内容描述 |
---|
名称 | “引用处理程序” | 可读流名称。 可以通过调用Thread对象的setName方法来指定名称。 并通过调用getName |
编号 | #2 | 分配给Thread类的每个对象的唯一ID。 为系统中的线程生成ID。 初始值为1。每个新创建的线程都分配有其自己的ID,该ID之前已增加1。可以使用Thread类的对象的getId函数获得此只读线程属性。 |
守护程序状态 | 守护程序 | 该标志表明线程是恶魔。 如果是恶魔,则将设置标志。 例如,线程-0不是守护程序。 |
优先权 | prio = 10 | Java流的数字优先级。 请注意,此优先级不一定与操作系统中关联线程的优先级相对应。 要设置优先级,您可以 使用Thread类的对象的setPriority方法并获取 getPriority方法。 |
操作系统线程优先级 | os_prio = 2 | 操作系统中的优先线程。 该优先级可能与分配给链接的Java线程的优先级不同。 |
住址 | tid = 0x00000250e4979000 | Java流的地址。 该地址是Thread类的Java本机接口(JNI)本机对象的指针(通过JNI连接到Java线程的C ++ Thread对象)。 该值是通过将指针投射到此值而获得的 (与此Java线程关联的C ++对象)为整数。 见 热点/共享/运行时/thread.cpp中的第879行 :
st-> print(“ tid =” INTPTR_FORMAT“”,p2i(this));
尽管此对象的键( tid )看起来像流ID, 实际上,这是JNI C ++线程连接对象的地址,而不是 返回Java 线程的getId方法。 |
操作系统线程ID | nid = 0x3c28 | Java线程绑定到的操作系统线程的唯一标识符。 该值使用以下代码输出: 热点/共享/运行时/osThread.cpp中的第42行 :
st-> print(“ nid = 0x%x”,thread_id());
|
现况 | 等待条件 | 当前线程的可读状态。 此行显示有关流的简单状态的其他信息(请参见下文),可以是 用于了解线程将要做什么(即线程是否试图获取锁) 或等待解锁条件得到满足)。 |
最新的Java堆栈指针 | [0x000000b82a9ff000] | 与此流关联的最后一个已知的堆栈指针(SP)。 该值是使用本机C ++代码和使用JNI的Java代码混合获得的。 该值由函数last_Java_sp()返回, 热点/共享/运行时/ thread.cpp中的第2886行 :
st-> print_cr(“ [” INTPTR_FORMAT“]”,
(intptr_t)last_Java_sp()&〜right_n_bits(12));
对于简单的线程转储,此信息几乎没有用。 但是,在复杂情况下,SP可能会 用于跟踪锁。 |
流状态
第二行是流的当前状态。 枚举列出了可能的流状态:
Thread.State :
新品
可运行
封锁
等待中
TIMED_WAITING
已终止
有关更多详细信息,请参见
文档 。
线程堆栈跟踪
下一部分包含进行转储时流的堆栈跟踪。 此堆栈跟踪与未捕获的异常引发的堆栈跟踪非常相似。 它包含转储形成时执行的类和字符串的名称。 对于引用处理程序流,我们看不到任何有趣的东西。
但是,关于Thread-02线程跟踪,有一点与标准跟踪不同:
“线程-0”#12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec等待监视器条目[0x000000b82b4ff000]
java.lang.Thread.State:已阻止(在对象监视器上)
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465b0>(一个java.lang.Object)
-锁定<0x00000000894465a0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
锁定的可拥有同步器:
-无
在跟踪中,我们看到已添加了有关锁的信息。 该线程期望对地址为0x00000000894465b0(对象类型为java.lang.Object)的对象进行锁定。 此外,线程本身持有地址为0x00000000894465a0的锁(也是java.lang.Object)。 此信息对以后对死锁的诊断很有用。
捕获的同步原语(可拥有的同步器)
最后一部分列出了流捕获的同步原语。 这些是可用于同步线程(例如锁)的对象。
根据Java的官方文档,
Ownable Synchronizer是
AbstractOwnableSynchronizer (或其子类)的后代,该流可以为同步目的而专门捕获它。
ReentrantLock和
write-lock而不是
ReentrantReadWriteLock类的
读取锁是平台提供的此类“拥有的同步器”的两个很好的示例。
有关此主题的更多信息,您可以参考此
发布 。
JVM线程
转储的下一部分包含有关JVM技术线程的信息,这些信息不是应用程序的一部分,而是与操作系统线程相关联的。 因为 这些流在应用程序外部工作,它们没有流标识符。 通常,这些是垃圾收集器线程和其他技术JVM线程:
“ VM线程” os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920可运行
“ GC线程号0” os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c可运行
“ GC线程#1” os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4可运行
“ GC线程#2” os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8可运行
“ GC线程#3” os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0可运行
“ G1主标记” os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068可运行
“ G1 Conc#0” os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28可运行
“ G1优化#0” os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c可运行
“ G1优化#1” os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890可运行
“ G1优化#2” os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8可运行
“ G1优化#3” os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00可运行
“ G1 Young RemSet采样” os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4可运行
“ VM定期任务线程” os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468等待条件
JNI全球链接
本部分指示JVM通过JNI使用的全局引用数。 这些链接不由垃圾收集器提供服务,在某些情况下可能导致内存泄漏。
JNI全球参考:2
在大多数简单情况下,不使用此信息。 但是,必须理解全局引用的重要性。 有关更多详细信息,请参见这篇
文章 。
死锁线程
最后一部分包含有关找到的死锁的信息。
如果找不到这些,则该部分将为空。 因为 我们专门开发了一个带锁的应用程序,在本例中就是这样。 在转储期间检测到锁定,并显示以下消息:
发现了一个Java级死锁:
==============================
“线程-0”:
等待锁定监视器0x00000250e4982480(对象0x00000000894465b0,一个java.lang.Object),
由“ Thread-1”持有
“线程1”:
等待锁定监视器0x00000250e4982380(对象0x00000000894465a0,一个java.lang.Object),
由“线程-0”持有
上面列出的线程的Java堆栈信息:
==================================================== =
“线程-0”:
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465b0>(一个java.lang.Object)
-锁定<0x00000000894465a0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
“线程1”:
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)
-等待锁定<0x00000000894465a0>(一个java.lang.Object)
-锁定<0x00000000894465b0>(一个java.lang.Object)
在java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
发现1个死锁。
第一部分描述死锁情况:
线程0期望能够捕获监视器(这是对我们应用程序中的
synced(secondResource)块的访问),与此同时,该线程持有试图捕获Thread-1的监视器(这正在访问相同的代码片段:
Synchronized(secondResource )。
将此圆形锁称为
死锁 。 在下面的图片中
这种情况以图形形式表示:

在第二小节中,给出了两个阻塞线程的堆栈跟踪。
该堆栈跟踪使我们可以跟踪每个线程的操作,直到发生锁定为止。
在我们的例子中,如果我们看一下这一行:
在DeadlockProgram $ DeadlockRunnable.run(DeadlockProgram.java:34)中 ,那么我们将看到代码的问题部分:
printLockedResource(secondResource);
该行是同步块的第一行,这是锁定的原因,并告诉我们,secondResource上的同步是相互锁定的原因。 为了解决这种情况,我们必须确保两个线程在资源resourceA和resourceB上具有相同的同步顺序。 如果这样做,我们将进入以下应用程序:
public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized (firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized (secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } }
该应用程序将在没有互锁的情况下结束,因此,我们将获得以下输出(请注意,Object类的地址已更改):
线程-0:锁定的资源-> java.lang.Object@1ad895d1
线程-0:锁定的资源-> java.lang.Object@6e41d7dd
线程1:锁定的资源-> java.lang.Object@1ad895d1
线程1:锁定的资源-> java.lang.Object@6e41d7dd
, , thread dump, . ( deadlock-). , .
Thread Dump-
.
JVM . ( , ).
.
- — Thread Dump Analyzers (TDAs). Java thread dump- - , . , . , .
TDA:
. .
结论
Thread dump- — Java-, . , .
deadlock, . . , — .
, Java- thread dump-. , .
, thread dump — « » , , Java-.
Java . , deadlock- ., .