View Javadoc
1   /*
2    * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE
3    * HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 only, as
6    * published by the Free Software Foundation. Oracle designates this particular file as subject to the "Classpath" exception as provided by
7    * Oracle in the LICENSE file that accompanied this code.
8    *
9    * This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version 2 for more details (a copy is included in
11   * the LICENSE file that accompanied this code).
12   *
13   * You should have received a copy of the GNU General Public License version 2 along with this work; if not, write to the Free Software
14   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
15   *
16   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA or visit www.oracle.com if you need additional information or
17   * have any questions.
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   * This FullThreadDump class demonstrates the capability to get a full thread dump and also detect deadlock remotely.
45   * <p>
46   * Based on code by Sun Microsystems, Inc. and Oracle.
47   *
48   * @author Josh Bloch
49   * @author Neal Gafter
50   * @author RealLifeDeveloper
51   */
52  @SuppressWarnings("PMD") // This builds on code from other sources
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       * Creates a new {@code FullThreadDump} object that is connected to a JMX server on the given host and port.
63       *
64       * @param hostname the name of the host running the JMX server
65       * @param port     the port number the JMX server is listening on
66       *
67       * @throws IOException if connection to the JMX server fails
68       */
69      public FullThreadDump(String hostname, int port) throws IOException {
70          LOG.info("Connecting to {}:{}", hostname.replaceAll("[\r\n]", ""), port);
71  
72          // Create an RMI connector client and connect it to
73          // the RMI connector server
74          String urlPath = "/jndi/rmi://" + hostname + ":" + port + "/jmxrmi";
75          JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
76          connect(url);
77      }
78  
79      /**
80       * Creates a new {@code FullThreadDump} object that is connected to a JMX server at the given {@code JMXServiceURL}.
81       *
82       * @param url the {@code JMXServiceURL} of the JMX server
83       *
84       * @throws IOException if connection to the JMX server fails
85       */
86      public FullThreadDump(JMXServiceURL url) throws IOException {
87          connect(url);
88      }
89  
90      /**
91       * Creates a thread dump with information about all the threads running in the Java process being monitored by the JMX server connected
92       * to.
93       * <p>
94       * The information is meant to be read by humans and is not easily parsable.
95       *
96       * @return a list of strings with information about the threads, e.g., thread name, call stack an so on.
97       *
98       * @throws IOException if communication with the JMX server fails
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      * Connect to a JMX agent of a given URL.
111      */
112     @SuppressWarnings("BanJNDI")
113     private void connect(JMXServiceURL url) throws IOException {
114         Map<String, Object> env = new HashMap<>();
115         // String[] credentials = { "controlRole", "control" };
116         // env.put(JMXConnector.CREDENTIALS, credentials);
117         // env.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory());
118         this.jmxc = JMXConnectorFactory.connect(url, env);
119         // this.jmxc = JMXConnectorFactory.connect(url);
120         this.server = jmxc.getMBeanServerConnection();
121     }
122 
123     /**
124      * Connects to a JMX server at the given host and port number and creates a thread dump that is logged on INFO level.
125      *
126      * @param args should be one string on the form "hostname:port", e.g., "localhost:4711"
127      *
128      * @throws IOException if communication with the JMX server fails
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); // TODO: Test this
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         // get full thread dump and perform deadlock detection
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      * Example of using the java.lang.management API to dump stack trace and to perform deadlock detection.
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          * Constructs a ThreadMonitor object to get thread information in a remote JVM.
175          */
176         ThreadMonitor(MBeanServerConnection server) throws IOException {
177             this.tmbean = newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME, ThreadMXBean.class);
178         }
179 
180         /**
181          * Gives the thread dump information as a list of strings, one line per string.
182          */
183         List<String> threadDump() {
184             if (tmbean.isObjectMonitorUsageSupported() && tmbean.isSynchronizerUsageSupported()) {
185                 // Print lock info if both object monitor usage
186                 // and synchronizer usage are supported.
187                 // This sample code can be modified to handle if
188                 // either monitor usage or synchronizer usage is supported.
189                 dumpThreadInfoWithLocks();
190             }
191             return THREAD_INFO;
192         }
193 
194         /**
195          * Saves the thread dump information with locks info in THREAD_INFO.
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          * Checks if any threads are deadlocked. If any, save the thread dump information.
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 }