【全网首发】将线程死锁检测集成进arthas以提高定位效率原创
1年前
422024
背景介绍
在日常排查过程中,有遇到线程死锁的问题,系统自带的jstack可以用来定位发生死锁的线程,而arthas作为日常分析排查问题不可缺少的利器,如果能够将线程死锁检测集成进arthas,线程死锁分析排查会更加方便和高效。
实现思路
jstack实现方式
以下程序主要由两个部分组成:
- 由synchronized和java.util.concurrent.Lock引起的线程死锁示例
- 线程死锁检测实现逻辑
产生线程死锁逻辑
import java.util.concurrent.locks.ReentrantLock;
public class DeadlockBuilder {
private static final ReentrantLock REENTRANT_LOCK_A = new ReentrantLock();
private static final ReentrantLock REENTRANT_LOCK_B = new ReentrantLock();
public static void deadlockByMonitor() {
Object a = new Object();
Object b = new Object();
Thread threadA = new Thread(() -> {
synchronized (a) {
sleep();
synchronized (b) {
}
}
});
threadA.setName("Thread_Monitor_A");
threadA.start();
Thread threadB = new Thread(() -> {
synchronized (b) {
sleep();
synchronized (a) {
}
}
});
threadB.setName("Thread_Monitor_B");
threadB.start();
}
public static void deadlockBySynchronizer() {
Thread threadA = new Thread(() -> {
REENTRANT_LOCK_A.lock();
try {
sleep();
REENTRANT_LOCK_B.lock();
try {
} finally {
REENTRANT_LOCK_B.unlock();
}
} finally {
REENTRANT_LOCK_A.unlock();
}
});
threadA.setName("Thread_Synchronizer_A");
threadA.start();
Thread threadB = new Thread(() -> {
REENTRANT_LOCK_B.lock();
try {
sleep();
REENTRANT_LOCK_A.lock();
try {
} finally {
REENTRANT_LOCK_A.unlock();
}
} finally {
REENTRANT_LOCK_B.unlock();
}
});
threadB.setName("Thread_Synchronizer_B");
threadB.start();
}
private static void sleep() {
try {
Thread.sleep(100L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
死锁检测实现逻辑
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.management.LockInfo;
import java.util.*;
public class DeadlockDetector {
private static final ThreadMXBean THREADMX_BEAN = ManagementFactory.getThreadMXBean();
public static void main(String[] args) throws Exception {
System.out.println(ManagementFactory.getRuntimeMXBean().getName());
DeadlockBuilder.deadlockByMonitor();
DeadlockBuilder.deadlockBySynchronizer();
Thread.sleep(1000);
DeadlockInfo deadlock = detect();
render(deadlock);
}
private static DeadlockInfo detect() {
int globalDfn = 0, thisDfn;
ThreadInfo currentThread;
ThreadInfo previousThread;
LockInfo waitingToLockInfo;
ThreadInfo[] infos = THREADMX_BEAN.dumpAllThreads(THREADMX_BEAN.isObjectMonitorUsageSupported(),
THREADMX_BEAN.isSynchronizerUsageSupported());
Map<ThreadInfo, Integer> threadTable = new HashMap<>();
Map<Integer, ThreadInfo> ownerThreadPerLock = new HashMap<>();
for (ThreadInfo threadInfo : infos) {
threadTable.put(threadInfo, -1);
for (MonitorInfo monitorInfo : threadInfo.getLockedMonitors()) {
ownerThreadPerLock.putIfAbsent(monitorInfo.getIdentityHashCode(), threadInfo);
}
for (LockInfo lockedSync : threadInfo.getLockedSynchronizers()) {
ownerThreadPerLock.putIfAbsent(lockedSync.getIdentityHashCode(), threadInfo);
}
}
DeadlockInfo deadlock = new DeadlockInfo();
deadlock.setOwnerThreadPerLock(ownerThreadPerLock);
for (Map.Entry<ThreadInfo, Integer> e : threadTable.entrySet()) {
Integer value = e.getValue();
if (value >= 0) {
// this thread was already visited
continue;
}
thisDfn = globalDfn;
ThreadInfo thread = e.getKey();
previousThread = thread;
waitingToLockInfo = thread.getLockInfo();
while (waitingToLockInfo != null) {
currentThread = ownerThreadPerLock.get(waitingToLockInfo.getIdentityHashCode());
if (currentThread == null) {
// No dependency on another thread
break;
}
Integer val = threadTable.get(thread);
int dfn = (val != null) ? val : -1;
if (dfn < 0) {
// First visit to this thread
threadTable.put(currentThread, globalDfn++);
} else if (dfn < thisDfn) {
// Thread already visited, and not on a (new) cycle
break;
} else if (currentThread == previousThread) {
// Self-loop, ignore
break;
} else {
// We have a (new) cycle
deadlock.getThreads().add(currentThread);
break;
}
previousThread = currentThread;
waitingToLockInfo = currentThread.getLockInfo();
}
}
return deadlock;
}
public static void render(DeadlockInfo deadlock){
Map<Integer, ThreadInfo> ownerThreadPerLock = deadlock.getOwnerThreadPerLock();
for(ThreadInfo thread : deadlock.getThreads()){
LockInfo waitingToLockInfo;
ThreadInfo currentThread = thread;
System.out.println("Found one Java-level deadlock:");
System.out.println("=============================");
do{
System.out.println(currentThread.getThreadName() + "(" + currentThread.getThreadId() + "):");
waitingToLockInfo = currentThread.getLockInfo();
if(waitingToLockInfo != null){
System.out.println(" waiting to lock info @" + waitingToLockInfo + ",");
System.out.print(" which is held by ");
currentThread = ownerThreadPerLock.get(waitingToLockInfo.getIdentityHashCode());
System.out.println(currentThread.getThreadName());
}
}while (!currentThread.equals(thread));
System.out.println();
}
int numberOfDeadlocks = deadlock.getThreads().size();
switch (numberOfDeadlocks) {
case 0:
System.out.println("No deadlocks found.");
break;
case 1:
System.out.println("Found a total of 1 deadlock.");
break;
default:
System.out.println("Found a total of " + numberOfDeadlocks + " deadlocks.");
break;
}
}
}
class DeadlockInfo{
private List<ThreadInfo> threads = new LinkedList<>();
private Map<Integer, ThreadInfo> ownerThreadPerLock = new HashMap<>();
public Map<Integer, ThreadInfo> getOwnerThreadPerLock() {
return ownerThreadPerLock;
}
public void setOwnerThreadPerLock(Map<Integer, ThreadInfo> ownerThreadPerLock) {
this.ownerThreadPerLock = ownerThreadPerLock;
}
public List<ThreadInfo> getThreads() {
return threads;
}
public void setThreads(List<ThreadInfo> threads) {
this.threads = threads;
}
}
直接运行上面的程序就可以看到效果了。
JMX实现方式
import java.lang.management.*;
public class ThreadMXDeadlockDetector {
private static final ThreadMXBean THREADMX_BEAN = ManagementFactory.getThreadMXBean();
public static void main(String[] args) throws Exception {
System.out.println(ManagementFactory.getRuntimeMXBean().getName());
DeadlockBuilder.deadlockByMonitor();
DeadlockBuilder.deadlockBySynchronizer();
Thread.sleep(1000);
DeadlockInfo deadlock = detect();
DeadlockDetector.render(deadlock);
}
private static DeadlockInfo detect(){
DeadlockInfo deadlockInfo = new DeadlockInfo();
long[] ids = THREADMX_BEAN.findDeadlockedThreads();
if(ids == null){
return deadlockInfo;
}
ThreadInfo[] threads = THREADMX_BEAN.getThreadInfo(ids,
THREADMX_BEAN.isObjectMonitorUsageSupported(),THREADMX_BEAN.isSynchronizerUsageSupported());
for(ThreadInfo threadInfo : threads){
LockInfo lockInfo = threadInfo.getLockInfo();
if(!deadlockInfo.getOwnerThreadPerLock().containsKey(lockInfo.getIdentityHashCode())){
deadlockInfo.getThreads().add(threadInfo);
}
for (MonitorInfo monitorInfo : threadInfo.getLockedMonitors()) {
deadlockInfo.getOwnerThreadPerLock().putIfAbsent(monitorInfo.getIdentityHashCode(), threadInfo);
}
for (LockInfo lockedSync : threadInfo.getLockedSynchronizers()) {
deadlockInfo.getOwnerThreadPerLock().putIfAbsent(lockedSync.getIdentityHashCode(), threadInfo);
}
}
return deadlockInfo;
}
}
集成进arthas以提高排查效率
arthas jvm命令显示了死锁线程个数,线程名称、线程ID并没有展示出来,如果想知道死锁线程具体信息需要再使用jstack来分析;
如果死锁线程信息可以在arthas中展示出来可以提高问题排查效率。
集成进arthas主要涉及以下几个类的修改:
- ThreadCommand中增加线程死锁检测指令@Option(shortName = “d”, longName = “deadlock”, flag = true)
- ThreadUtil中增加线程死锁检测的实现方法
- ThreadView中增加线程死锁检测的结果展示逻辑
然后编译打包,运行上面死锁生成的代码,使用arthas thread -d命令效果如下:
总结
- 构造由synchronized和java.util.concurrent.Lock引起线程死锁的示例;
- jstack及JMX实现线程死锁检测的逻辑;
- 将线程死锁检测集成进arthas。
点赞收藏
分类: