1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.reallifedeveloper.tools;
21
22 import static java.lang.management.ManagementFactory.THREAD_MXBEAN_NAME;
23 import static java.lang.management.ManagementFactory.newPlatformMXBeanProxy;
24
25 import java.io.IOException;
26 import java.lang.management.LockInfo;
27 import java.lang.management.MonitorInfo;
28 import java.lang.management.ThreadInfo;
29 import java.lang.management.ThreadMXBean;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34
35 import javax.management.MBeanServerConnection;
36 import javax.management.remote.JMXConnector;
37 import javax.management.remote.JMXConnectorFactory;
38 import javax.management.remote.JMXServiceURL;
39
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43
44
45
46
47
48
49
50
51
52 @SuppressWarnings("PMD")
53 public final class FullThreadDump {
54
55 private static final Logger LOG = LoggerFactory.getLogger(FullThreadDump.class);
56
57 private MBeanServerConnection server;
58
59 private JMXConnector jmxc;
60
61
62
63
64
65
66
67
68
69 public FullThreadDump(String hostname, int port) throws IOException {
70 LOG.info("Connecting to {}:{}", hostname.replaceAll("[\r\n]", ""), port);
71
72
73
74 String urlPath = "/jndi/rmi://" + hostname + ":" + port + "/jmxrmi";
75 JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
76 connect(url);
77 }
78
79
80
81
82
83
84
85
86 public FullThreadDump(JMXServiceURL url) throws IOException {
87 connect(url);
88 }
89
90
91
92
93
94
95
96
97
98
99
100 public List<String> dump() throws IOException {
101 ThreadMonitor monitor = new ThreadMonitor(server);
102 List<String> threadInfo = monitor.threadDump();
103 if (!monitor.findDeadlock()) {
104 threadInfo.add("No deadlock found.");
105 }
106 return threadInfo;
107 }
108
109
110
111
112 @SuppressWarnings("BanJNDI")
113 private void connect(JMXServiceURL url) throws IOException {
114 Map<String, Object> env = new HashMap<>();
115
116
117
118 this.jmxc = JMXConnectorFactory.connect(url, env);
119
120 this.server = jmxc.getMBeanServerConnection();
121 }
122
123
124
125
126
127
128
129
130 public static void main(String... args) throws IOException {
131 if (args == null || args.length != 1) {
132 throw new IllegalArgumentException(usage());
133 }
134
135 String[] arg2 = args[0].split(":", -1);
136 if (arg2.length != 2) {
137 throw new IllegalArgumentException(usage());
138 }
139 String hostname = arg2[0];
140 int port = -1;
141 try {
142 port = Integer.parseInt(arg2[1]);
143 } catch (NumberFormatException x) {
144 throw new IllegalArgumentException(usage());
145 }
146 if (port < 0) {
147 throw new IllegalArgumentException(usage());
148 }
149
150
151 FullThreadDump ftd = new FullThreadDump(hostname, port);
152 List<String> threadInfo = ftd.dump();
153 for (String threadInfoLine : threadInfo) {
154 LOG.info(threadInfoLine.replaceAll("[\r\n]", ""));
155 }
156 }
157
158 private static String usage() {
159 return "Usage: java " + FullThreadDump.class.getName() + " <hostname>:<port>";
160 }
161
162
163
164
165 private static final class ThreadMonitor {
166
167 private static final List<String> THREAD_INFO = new ArrayList<>();
168
169 private static final String INDENT = " ";
170
171 private ThreadMXBean tmbean;
172
173
174
175
176 ThreadMonitor(MBeanServerConnection server) throws IOException {
177 this.tmbean = newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME, ThreadMXBean.class);
178 }
179
180
181
182
183 List<String> threadDump() {
184 if (tmbean.isObjectMonitorUsageSupported() && tmbean.isSynchronizerUsageSupported()) {
185
186
187
188
189 dumpThreadInfoWithLocks();
190 }
191 return THREAD_INFO;
192 }
193
194
195
196
197 private void dumpThreadInfoWithLocks() {
198 THREAD_INFO.add("Full Java thread dump with locks info");
199
200 ThreadInfo[] tinfos = tmbean.dumpAllThreads(true, true);
201 for (ThreadInfo ti : tinfos) {
202 printThreadInfo(ti);
203 LockInfo[] syncs = ti.getLockedSynchronizers();
204 addLockInfo(syncs);
205 }
206 }
207
208 private void printThreadInfo(ThreadInfo ti) {
209 addThreadInfo(ti);
210
211 StackTraceElement[] stacktrace = ti.getStackTrace();
212 MonitorInfo[] monitors = ti.getLockedMonitors();
213 for (int i = 0; i < stacktrace.length; i++) {
214 StackTraceElement ste = stacktrace[i];
215 THREAD_INFO.add(INDENT + "at " + ste.toString());
216 for (MonitorInfo mi : monitors) {
217 if (mi.getLockedStackDepth() == i) {
218 THREAD_INFO.add(INDENT + " - locked " + mi);
219 }
220 }
221 }
222 }
223
224 private void addThreadInfo(ThreadInfo ti) {
225 StringBuilder sb = new StringBuilder(
226 "\"" + ti.getThreadName() + "\"" + " Id=" + ti.getThreadId() + " in " + ti.getThreadState());
227 if (ti.getLockName() != null) {
228 sb.append(" on lock=" + ti.getLockName());
229 }
230 if (ti.isSuspended()) {
231 sb.append(" (suspended)");
232 }
233 if (ti.isInNative()) {
234 sb.append(" (running in native)");
235 }
236 THREAD_INFO.add(sb.toString());
237 if (ti.getLockOwnerName() != null) {
238 THREAD_INFO.add(INDENT + " owned by " + ti.getLockOwnerName() + " Id=" + ti.getLockOwnerId());
239 }
240 }
241
242 private void addLockInfo(LockInfo[] locks) {
243 THREAD_INFO.add(INDENT + "Locked synchronizers: count = " + locks.length);
244 for (LockInfo li : locks) {
245 THREAD_INFO.add(INDENT + " - " + li);
246 }
247 }
248
249
250
251
252 boolean findDeadlock() {
253 long[] tids = tmbean.findDeadlockedThreads();
254 if (tids == null) {
255 return false;
256 }
257
258 THREAD_INFO.add("Deadlock found :-");
259 ThreadInfo[] infos = tmbean.getThreadInfo(tids, true, true);
260 for (ThreadInfo ti : infos) {
261 printThreadInfo(ti);
262 addLockInfo(ti.getLockedSynchronizers());
263 }
264
265 return true;
266 }
267 }
268 }