FullThreadDump.java
/*
* Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE
* HEADER.
*
* 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
* published by the Free Software Foundation. Oracle designates this particular file as subject to the "Classpath" exception as provided by
* Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version 2 for more details (a copy is included in
* the LICENSE file that accompanied this code).
*
* 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
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.reallifedeveloper.tools;
import static java.lang.management.ManagementFactory.THREAD_MXBEAN_NAME;
import static java.lang.management.ManagementFactory.newPlatformMXBeanProxy;
import java.io.IOException;
import java.lang.management.LockInfo;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This FullThreadDump class demonstrates the capability to get a full thread dump and also detect deadlock remotely.
* <p>
* Based on code by Sun Microsystems, Inc. and Oracle.
*
* @author Josh Bloch
* @author Neal Gafter
* @author RealLifeDeveloper
*/
@SuppressWarnings("PMD") // This builds on code from other sources
public final class FullThreadDump {
private static final Logger LOG = LoggerFactory.getLogger(FullThreadDump.class);
private MBeanServerConnection server;
private JMXConnector jmxc;
/**
* Creates a new {@code FullThreadDump} object that is connected to a JMX server on the given host and port.
*
* @param hostname the name of the host running the JMX server
* @param port the port number the JMX server is listening on
*
* @throws IOException if connection to the JMX server fails
*/
public FullThreadDump(String hostname, int port) throws IOException {
LOG.info("Connecting to {}:{}", hostname, port);
// Create an RMI connector client and connect it to
// the RMI connector server
String urlPath = "/jndi/rmi://" + hostname + ":" + port + "/jmxrmi";
try {
JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
connect(url);
} catch (MalformedURLException cause) {
// Should never happen
InternalError ie = new InternalError(cause.getMessage());
ie.initCause(cause);
throw ie;
}
}
/**
* Creates a new {@code FullThreadDump} object that is connected to a JMX server at the given {@code JMXServiceURL}.
*
* @param url the {@code JMXServiceURL} of the JMX server
*
* @throws IOException if connection to the JMX server fails
*/
public FullThreadDump(JMXServiceURL url) throws IOException {
connect(url);
}
/**
* Creates a thread dump with information about all the threads running in the Java process being monitored by the JMX server connected
* to.
* <p>
* The information is meant to be read by humans and is not easily parsable.
*
* @return a list of strings with information about the threads, e.g., thread name, call stack an so on.
*
* @throws IOException if communication with the JMX server fails
*/
public List<String> dump() throws IOException {
ThreadMonitor monitor = new ThreadMonitor(server);
List<String> threadInfo = monitor.threadDump();
if (!monitor.findDeadlock()) {
threadInfo.add("No deadlock found.");
}
return threadInfo;
}
/**
* Connect to a JMX agent of a given URL.
*
* @throws IOException
*/
private void connect(JMXServiceURL url) throws IOException {
Map<String, Object> env = new HashMap<>();
// String[] credentials = { "controlRole", "control" };
// env.put(JMXConnector.CREDENTIALS, credentials);
// env.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory());
this.jmxc = JMXConnectorFactory.connect(url, env);
// this.jmxc = JMXConnectorFactory.connect(url);
this.server = jmxc.getMBeanServerConnection();
}
/**
* Connects to a JMX server at the given host and port number and creates a thread dump that is logged on INFO level.
*
* @param args should be one string on the form "hostname:port", e.g., "localhost:4711"
*
* @throws IOException if communication with the JMX server fails
*/
public static void main(String... args) throws IOException {
if (args == null || args.length != 1) {
throw new IllegalArgumentException(usage());
}
String[] arg2 = args[0].split(":");
if (arg2.length != 2) {
throw new IllegalArgumentException(usage());
}
String hostname = arg2[0];
int port = -1;
try {
port = Integer.parseInt(arg2[1]);
} catch (NumberFormatException x) {
throw new IllegalArgumentException(usage());
}
if (port < 0) {
throw new IllegalArgumentException(usage());
}
// get full thread dump and perform deadlock detection
FullThreadDump ftd = new FullThreadDump(hostname, port);
List<String> threadInfo = ftd.dump();
for (String threadInfoLine : threadInfo) {
LOG.info(threadInfoLine);
}
}
private static String usage() {
return "Usage: java " + FullThreadDump.class.getName() + " <hostname>:<port>";
}
/**
* Example of using the java.lang.management API to dump stack trace and to perform deadlock detection.
*/
private static final class ThreadMonitor {
private static final List<String> THREAD_INFO = new ArrayList<>();
private static final String INDENT = " ";
private ThreadMXBean tmbean;
/**
* Constructs a ThreadMonitor object to get thread information in a remote JVM.
*/
ThreadMonitor(MBeanServerConnection server) throws IOException {
this.tmbean = newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME, ThreadMXBean.class);
}
/**
* Gives the thread dump information as a list of strings, one line per string.
*/
public List<String> threadDump() {
if (tmbean.isObjectMonitorUsageSupported() && tmbean.isSynchronizerUsageSupported()) {
// Print lock info if both object monitor usage
// and synchronizer usage are supported.
// This sample code can be modified to handle if
// either monitor usage or synchronizer usage is supported.
dumpThreadInfoWithLocks();
}
return THREAD_INFO;
}
/**
* Saves the thread dump information with locks info in THREAD_INFO.
*/
private void dumpThreadInfoWithLocks() {
THREAD_INFO.add("Full Java thread dump with locks info");
ThreadInfo[] tinfos = tmbean.dumpAllThreads(true, true);
for (ThreadInfo ti : tinfos) {
printThreadInfo(ti);
LockInfo[] syncs = ti.getLockedSynchronizers();
addLockInfo(syncs);
}
}
private void printThreadInfo(ThreadInfo ti) {
addThreadInfo(ti);
StackTraceElement[] stacktrace = ti.getStackTrace();
MonitorInfo[] monitors = ti.getLockedMonitors();
for (int i = 0; i < stacktrace.length; i++) {
StackTraceElement ste = stacktrace[i];
THREAD_INFO.add(INDENT + "at " + ste.toString());
for (MonitorInfo mi : monitors) {
if (mi.getLockedStackDepth() == i) {
THREAD_INFO.add(INDENT + " - locked " + mi);
}
}
}
}
private void addThreadInfo(ThreadInfo ti) {
StringBuilder sb = new StringBuilder(
"\"" + ti.getThreadName() + "\"" + " Id=" + ti.getThreadId() + " in " + ti.getThreadState());
if (ti.getLockName() != null) {
sb.append(" on lock=" + ti.getLockName());
}
if (ti.isSuspended()) {
sb.append(" (suspended)");
}
if (ti.isInNative()) {
sb.append(" (running in native)");
}
THREAD_INFO.add(sb.toString());
if (ti.getLockOwnerName() != null) {
THREAD_INFO.add(INDENT + " owned by " + ti.getLockOwnerName() + " Id=" + ti.getLockOwnerId());
}
}
private void addLockInfo(LockInfo[] locks) {
THREAD_INFO.add(INDENT + "Locked synchronizers: count = " + locks.length);
for (LockInfo li : locks) {
THREAD_INFO.add(INDENT + " - " + li);
}
}
/**
* Checks if any threads are deadlocked. If any, save the thread dump information.
*/
public boolean findDeadlock() {
long[] tids = tmbean.findDeadlockedThreads();
if (tids == null) {
return false;
}
THREAD_INFO.add("Deadlock found :-");
ThreadInfo[] infos = tmbean.getThreadInfo(tids, true, true);
for (ThreadInfo ti : infos) {
printThreadInfo(ti);
addLockInfo(ti.getLockedSynchronizers());
}
return true;
}
}
}