提交 d2117a53 authored 作者: noelgrandin's avatar noelgrandin

improvements to AbbaLockingDetector

上级 9f3c4fca
...@@ -26,11 +26,14 @@ public class AbbaDetector { ...@@ -26,11 +26,14 @@ public class AbbaDetector {
return new ArrayDeque<Object>(); return new ArrayDeque<Object>();
} }
}; };
private static final Map<Object, Map<Object, Exception>> ORDER = /**
* Map of (object A) -> ( map of (object locked before object A) -> (stacktrace where locked) )
*/
private static final Map<Object, Map<Object, Exception>> LOCK_ORDERING =
new WeakHashMap<Object, Map<Object, Exception>>(); new WeakHashMap<Object, Map<Object, Exception>>();
private static final Set<String> KNOWN = new HashSet<String>(); private static final Set<String> KNOWN_DEADLOCKS = new HashSet<String>();
public static Object begin(Object o) { public static Object begin(Object o) {
if (o == null) { if (o == null) {
...@@ -40,6 +43,8 @@ public class AbbaDetector { ...@@ -40,6 +43,8 @@ public class AbbaDetector {
} }
Deque<Object> stack = STACK.get(); Deque<Object> stack = STACK.get();
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
// Ignore locks which are locked multiple times in succession -
// Java locks are recursive
if (stack.contains(o)) { if (stack.contains(o)) {
// already synchronized on this // already synchronized on this
return o; return o;
...@@ -56,7 +61,7 @@ public class AbbaDetector { ...@@ -56,7 +61,7 @@ public class AbbaDetector {
String thread = "[thread " + Thread.currentThread().getId() + "]"; String thread = "[thread " + Thread.currentThread().getId() + "]";
String ident = new String(new char[stack.size() * 2]).replace((char) 0, ' '); String ident = new String(new char[stack.size() * 2]).replace((char) 0, ' ');
System.out.println(thread + " " + ident + System.out.println(thread + " " + ident +
"sync " + getName(o)); "sync " + getObjectName(o));
} }
if (stack.size() > 0) { if (stack.size() > 0) {
markHigher(o, stack); markHigher(o, stack);
...@@ -70,16 +75,16 @@ public class AbbaDetector { ...@@ -70,16 +75,16 @@ public class AbbaDetector {
return o; return o;
} }
private static String getName(Object o) { private static String getObjectName(Object o) {
return o.getClass().getSimpleName() + ":" + System.identityHashCode(o); return o.getClass().getSimpleName() + "@" + System.identityHashCode(o);
} }
public static synchronized void markHigher(Object o, Deque<Object> older) { public static synchronized void markHigher(Object o, Deque<Object> older) {
Object test = getTest(o); Object test = getTest(o);
Map<Object, Exception> map = ORDER.get(test); Map<Object, Exception> map = LOCK_ORDERING.get(test);
if (map == null) { if (map == null) {
map = new WeakHashMap<Object, Exception>(); map = new WeakHashMap<Object, Exception>();
ORDER.put(test, map); LOCK_ORDERING.put(test, map);
} }
Exception oldException = null; Exception oldException = null;
for (Object old : older) { for (Object old : older) {
...@@ -87,20 +92,20 @@ public class AbbaDetector { ...@@ -87,20 +92,20 @@ public class AbbaDetector {
if (oldTest == test) { if (oldTest == test) {
continue; continue;
} }
Map<Object, Exception> oldMap = ORDER.get(oldTest); Map<Object, Exception> oldMap = LOCK_ORDERING.get(oldTest);
if (oldMap != null) { if (oldMap != null) {
Exception e = oldMap.get(test); Exception e = oldMap.get(test);
if (e != null) { if (e != null) {
String deadlockType = test.getClass() + " " + oldTest.getClass(); String deadlockType = test.getClass() + " " + oldTest.getClass();
if (!KNOWN.contains(deadlockType)) { if (!KNOWN_DEADLOCKS.contains(deadlockType)) {
String message = getName(test) + String message = getObjectName(test) +
" synchronized after \n " + getName(oldTest) + " synchronized after \n " + getObjectName(oldTest) +
", but in the past before"; ", but in the past before";
RuntimeException ex = new RuntimeException(message); RuntimeException ex = new RuntimeException(message);
ex.initCause(e); ex.initCause(e);
ex.printStackTrace(System.out); ex.printStackTrace(System.out);
// throw ex; // throw ex;
KNOWN.add(deadlockType); KNOWN_DEADLOCKS.add(deadlockType);
} }
} }
} }
......
...@@ -10,47 +10,30 @@ import java.lang.management.MonitorInfo; ...@@ -10,47 +10,30 @@ import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo; import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean; import java.lang.management.ThreadMXBean;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap;
/** /**
* A simple CPU profiling tool similar to java -Xrunhprof. It can be used * Utility to detect AB-BA deadlocks.
* in-process (to profile the current application) or as a standalone program
* (to profile a different process, or files containing full thread dumps).
*/ */
public class AbbaLockingDetector implements Runnable { public class AbbaLockingDetector implements Runnable {
private int tickInterval_ms = 2; private int tickInterval_ms = 2;
private volatile boolean stop; private volatile boolean stop;
private final Set<String> lockObjectNames = new HashSet<String>();
/**
* Necessary because String[] does not implement a deep equals and hashcode.
*/
private static final class LockOrder {
public final String[] names;
public LockOrder(String[] names) {
this.names = names;
}
@Override
public boolean equals(Object obj) {
return Arrays.deepEquals(names, ((LockOrder) obj).names);
}
@Override
public int hashCode() {
return Arrays.deepHashCode(names);
}
}
private final Set<LockOrder> lockObjectOrders = new HashSet<LockOrder>();
private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); private final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
private Thread thread; private Thread thread;
/**
* Map of (object A) -> ( map of (object locked before object A) ->
* (stacktrace where locked) )
*/
private final Map<String, Map<String, String>> LOCK_ORDERING = new WeakHashMap<String, Map<String, String>>();
private final Set<String> KNOWN_DEADLOCKS = new HashSet<String>();
/** /**
* Start collecting locking data. * Start collecting locking data.
* *
...@@ -101,44 +84,149 @@ public class AbbaLockingDetector implements Runnable { ...@@ -101,44 +84,149 @@ public class AbbaLockingDetector implements Runnable {
} }
} }
ThreadInfo[] list = threadMXBean.dumpAllThreads(true/* lockedMonitors */, true/* lockedSynchronizers */); ThreadInfo[] list = threadMXBean.dumpAllThreads(true/* lockedMonitors */, false/* lockedSynchronizers */);
processList(list); processThreadList(list);
} }
private void processList(ThreadInfo[] threadInfoList) { private void processThreadList(ThreadInfo[] threadInfoList) {
List<String> lockOrder = new ArrayList<String>(); final List<String> lockOrder = new ArrayList<String>();
for (ThreadInfo threadInfo : threadInfoList) { for (ThreadInfo threadInfo : threadInfoList) {
MonitorInfo[] monitorInfoList = threadInfo.getLockedMonitors();
lockOrder.clear(); lockOrder.clear();
for (MonitorInfo monitorInfo : monitorInfoList) { generateOrdering(lockOrder, threadInfo);
String lockName = monitorInfo.getClassName(); if (lockOrder.size() > 1) {
if (lockName.startsWith("org.h2")) { markHigher(lockOrder, threadInfo);
lockObjectNames.add(lockName); }
// ignore locks which are locked multiple times in succession - Java locks are recursive }
if (lockOrder.isEmpty() || !lockOrder.get(lockOrder.size() - 1).equals(lockName)) { }
/**
* We cannot simply call getLockedMonitors because it is not guaranteed to
* return the locks in the correct order.
*/
private static void generateOrdering(final List<String> lockOrder, ThreadInfo info) {
final MonitorInfo[] lockedMonitors = info.getLockedMonitors();
final StackTraceElement[] stackTrace = info.getStackTrace();
for (int i = stackTrace.length - 1; i >= 0; i--) {
for (MonitorInfo mi : lockedMonitors) {
if (mi.getLockedStackDepth() == i) {
String lockName = getObjectName(mi);
if (lockName.equals("sun.misc.Launcher$AppClassLoader")) {
// ignore, it shows up everywhere
continue;
}
// Ignore locks which are locked multiple times in
// succession - Java locks are recursive.
if (!lockOrder.contains(lockName)) {
lockOrder.add(lockName); lockOrder.add(lockName);
} }
} }
} }
if (lockOrder.size()>1) { }
lockObjectOrders.add(new LockOrder(lockOrder.toArray(new String[lockOrder.size()]))); }
private void markHigher(List<String> lockOrder, ThreadInfo threadInfo) {
String topLock = lockOrder.get(lockOrder.size() - 1);
Map<String, String> map = LOCK_ORDERING.get(topLock);
if (map == null) {
map = new WeakHashMap<String, String>();
LOCK_ORDERING.put(topLock, map);
}
String oldException = null;
for (int i = 0; i < lockOrder.size() - 1; i++) {
String olderLock = lockOrder.get(i);
Map<String, String> oldMap = LOCK_ORDERING.get(olderLock);
boolean foundDeadLock = false;
if (oldMap != null) {
String e = oldMap.get(topLock);
if (e != null) {
foundDeadLock = true;
String deadlockType = topLock + " " + olderLock;
if (!KNOWN_DEADLOCKS.contains(deadlockType)) {
System.out.println(topLock + " synchronized after \n " + olderLock
+ ", but in the past before\n" + "AFTER\n" + getStackTraceForThread(threadInfo)
+ "BEFORE\n" + e);
KNOWN_DEADLOCKS.add(deadlockType);
}
}
}
if (!foundDeadLock && !map.containsKey(olderLock)) {
if (oldException == null) {
oldException = getStackTraceForThread(threadInfo);
}
map.put(olderLock, oldException);
} }
} }
} }
public void dump() { /**
stopCollecting(); * Dump data in the same format as {@link ThreadInfo#toString()}, but with
System.out.println("Locked Objects"); * some modifications (no stack frame limit, and removal of uninteresting
System.out.println("--------------------------------"); * stack frames)
for (String s : lockObjectNames) { */
System.out.println(s); private static String getStackTraceForThread(ThreadInfo info) {
StringBuilder sb = new StringBuilder("\"" + info.getThreadName() + "\"" + " Id=" + info.getThreadId() + " "
+ info.getThreadState());
if (info.getLockName() != null) {
sb.append(" on " + info.getLockName());
}
if (info.getLockOwnerName() != null) {
sb.append(" owned by \"" + info.getLockOwnerName() + "\" Id=" + info.getLockOwnerId());
}
if (info.isSuspended()) {
sb.append(" (suspended)");
}
if (info.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
StackTraceElement[] stackTrace = info.getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
boolean dumpedStackTraceElement = false;
StackTraceElement ste = stackTrace[i];
MonitorInfo[] lockedMonitors = info.getLockedMonitors();
for (MonitorInfo mi : lockedMonitors) {
if (mi.getLockedStackDepth() == i) {
// Only start dumping the stack from the first time we lock
// something.
// Removes a lot of unnecessary noise from the output.
if (!dumpedStackTraceElement) {
dumpStackTraceElement(info, sb, i, ste);
dumpedStackTraceElement = true;
}
sb.append("\t- locked " + mi);
sb.append('\n');
}
}
} }
System.out.println(); return sb.toString();
System.out.println("Locking Orders"); }
System.out.println("--------------------------------");
for (LockOrder s : lockObjectOrders) { private static void dumpStackTraceElement(ThreadInfo info, StringBuilder sb, int i, StackTraceElement ste) {
System.out.println(Arrays.toString(s.names)); sb.append("\tat " + ste.toString());
sb.append('\n');
if (i == 0 && info.getLockInfo() != null) {
Thread.State ts = info.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on " + info.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on " + info.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on " + info.getLockInfo());
sb.append('\n');
break;
default:
}
} }
} }
private static String getObjectName(MonitorInfo info) {
return info.getClassName();// + "@" + info.getIdentityHashCode();
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论