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

improvements to AbbaLockingDetector

上级 9f3c4fca
......@@ -27,10 +27,13 @@ public class AbbaDetector {
}
};
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>>();
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) {
if (o == null) {
......@@ -40,6 +43,8 @@ public class AbbaDetector {
}
Deque<Object> stack = STACK.get();
if (!stack.isEmpty()) {
// Ignore locks which are locked multiple times in succession -
// Java locks are recursive
if (stack.contains(o)) {
// already synchronized on this
return o;
......@@ -56,7 +61,7 @@ public class AbbaDetector {
String thread = "[thread " + Thread.currentThread().getId() + "]";
String ident = new String(new char[stack.size() * 2]).replace((char) 0, ' ');
System.out.println(thread + " " + ident +
"sync " + getName(o));
"sync " + getObjectName(o));
}
if (stack.size() > 0) {
markHigher(o, stack);
......@@ -70,16 +75,16 @@ public class AbbaDetector {
return o;
}
private static String getName(Object o) {
return o.getClass().getSimpleName() + ":" + System.identityHashCode(o);
private static String getObjectName(Object o) {
return o.getClass().getSimpleName() + "@" + System.identityHashCode(o);
}
public static synchronized void markHigher(Object o, Deque<Object> older) {
Object test = getTest(o);
Map<Object, Exception> map = ORDER.get(test);
Map<Object, Exception> map = LOCK_ORDERING.get(test);
if (map == null) {
map = new WeakHashMap<Object, Exception>();
ORDER.put(test, map);
LOCK_ORDERING.put(test, map);
}
Exception oldException = null;
for (Object old : older) {
......@@ -87,20 +92,20 @@ public class AbbaDetector {
if (oldTest == test) {
continue;
}
Map<Object, Exception> oldMap = ORDER.get(oldTest);
Map<Object, Exception> oldMap = LOCK_ORDERING.get(oldTest);
if (oldMap != null) {
Exception e = oldMap.get(test);
if (e != null) {
String deadlockType = test.getClass() + " " + oldTest.getClass();
if (!KNOWN.contains(deadlockType)) {
String message = getName(test) +
" synchronized after \n " + getName(oldTest) +
if (!KNOWN_DEADLOCKS.contains(deadlockType)) {
String message = getObjectName(test) +
" synchronized after \n " + getObjectName(oldTest) +
", but in the past before";
RuntimeException ex = new RuntimeException(message);
ex.initCause(e);
ex.printStackTrace(System.out);
// throw ex;
KNOWN.add(deadlockType);
KNOWN_DEADLOCKS.add(deadlockType);
}
}
}
......
......@@ -10,47 +10,30 @@ import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
* A simple CPU profiling tool similar to java -Xrunhprof. It can be used
* in-process (to profile the current application) or as a standalone program
* (to profile a different process, or files containing full thread dumps).
* Utility to detect AB-BA deadlocks.
*/
public class AbbaLockingDetector implements Runnable {
private int tickInterval_ms = 2;
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 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.
*
......@@ -101,44 +84,149 @@ public class AbbaLockingDetector implements Runnable {
}
}
ThreadInfo[] list = threadMXBean.dumpAllThreads(true/* lockedMonitors */, true/* lockedSynchronizers */);
processList(list);
ThreadInfo[] list = threadMXBean.dumpAllThreads(true/* lockedMonitors */, false/* lockedSynchronizers */);
processThreadList(list);
}
private void processList(ThreadInfo[] threadInfoList) {
List<String> lockOrder = new ArrayList<String>();
private void processThreadList(ThreadInfo[] threadInfoList) {
final List<String> lockOrder = new ArrayList<String>();
for (ThreadInfo threadInfo : threadInfoList) {
MonitorInfo[] monitorInfoList = threadInfo.getLockedMonitors();
lockOrder.clear();
for (MonitorInfo monitorInfo : monitorInfoList) {
String lockName = monitorInfo.getClassName();
if (lockName.startsWith("org.h2")) {
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)) {
generateOrdering(lockOrder, threadInfo);
if (lockOrder.size() > 1) {
markHigher(lockOrder, threadInfo);
}
}
}
/**
* 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);
}
}
}
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();
System.out.println("Locked Objects");
System.out.println("--------------------------------");
for (String s : lockObjectNames) {
System.out.println(s);
/**
* Dump data in the same format as {@link ThreadInfo#toString()}, but with
* some modifications (no stack frame limit, and removal of uninteresting
* stack frames)
*/
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');
}
}
}
return sb.toString();
}
private static void dumpStackTraceElement(ThreadInfo info, StringBuilder sb, int i, StackTraceElement ste) {
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:
}
System.out.println();
System.out.println("Locking Orders");
System.out.println("--------------------------------");
for (LockOrder s : lockObjectOrders) {
System.out.println(Arrays.toString(s.names));
}
}
private static String getObjectName(MonitorInfo info) {
return info.getClassName();// + "@" + info.getIdentityHashCode();
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论