Java tutorial
/** * ========================================================================================== * = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION = * ========================================================================================== * * http://www.jahia.com * * Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved. * * THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES: * 1/GPL OR 2/JSEL * * 1/ GPL * ================================================================================== * * IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * 2/ JSEL - Commercial and Supported Versions of the program * =================================================================================== * * IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * Alternatively, commercial and supported versions of the program - also known as * Enterprise Distributions - must be used in accordance with the terms and conditions * contained in a separate written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ package org.jahia.tools.jvm; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; import java.lang.management.ThreadInfo; import java.util.Date; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import javax.management.*; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.time.FastDateFormat; import org.jahia.bin.errors.ErrorFileDumper; import org.jahia.bin.listeners.JahiaContextLoaderListener; import org.jahia.settings.SettingsBean; import org.jahia.utils.Patterns; import org.slf4j.Logger; import java.io.*; import java.util.concurrent.atomic.AtomicBoolean; /** * Warning : generating thread dumps is an operation that locks the JVM and therefore should not be done while * high load is running on the system. */ public class ThreadMonitor { private static final Logger logger = org.slf4j.LoggerFactory.getLogger(ThreadMonitor.class); public static final String THREAD_MONITOR_DEACTIVATED = "ThreadMonitor deactivated."; private class ThreadDumpTask extends TimerTask { private int executionCount; private int numberOfExecutions; private File targetFile; private boolean toSystemOut; ThreadDumpTask(int numberOfExecutions, boolean toSystemOut, File targetFile) { super(); this.numberOfExecutions = numberOfExecutions; this.targetFile = targetFile; this.toSystemOut = toSystemOut; out("Starting thread dump task for " + numberOfExecutions + " executions into a file " + targetFile); } @Override public void run() { executionCount++; if (executionCount > numberOfExecutions) { return; } out("Executing thread dump " + executionCount + " of " + numberOfExecutions); OutputStream out = null; long startTime = System.currentTimeMillis(); try { String dump = ThreadMonitor.getInstance().getFullThreadInfo(); if (toSystemOut) { System.out.println(dump); } if (targetFile != null) { out = new FileOutputStream(targetFile, true); out.write(dump.getBytes("UTF-8")); out.flush(); } } catch (UnsupportedEncodingException e) { logger.error(e.getMessage(), e); } catch (IOException e) { logger.error(e.getMessage(), e); } finally { if (targetFile != null) { IOUtils.closeQuietly(out); long dumpTime = System.currentTimeMillis() - startTime; debug("Appended thread dump to file " + targetFile.getAbsolutePath() + " in " + dumpTime + " ms"); } if (executionCount >= numberOfExecutions) { cancelTimer(); out("Stopping thread dump task after " + executionCount + " executions into a file " + targetFile); ThreadMonitor.getInstance().releaseAlreadyDumping(); } } } } private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); private static final String DUMP_END = "\n<EndOfDump>\n\n"; private static String INDENT = " "; private static File getNextThreadDumpFile(String postfix) { Date now = new Date(); File todaysDirectory = new File(SettingsBean.getThreadDir(), ErrorFileDumper.DATE_FORMAT_DIRECTORY.format(now)); todaysDirectory.mkdirs(); return new File(todaysDirectory, "thread-dump-" + ErrorFileDumper.DATE_FORMAT_FILE.format(now) + (postfix != null ? postfix : "") + ".out"); } private void debug(String msg) { if (debugLogging) { System.out.println(msg); } } private static void out(String msg) { System.out.println(msg); } private String dumpPrefix = "\nFull thread dump "; private ThreadMXBean tmbean; private volatile static ThreadMonitor instance; private Timer timer; private boolean debugLogging = false; private AtomicBoolean alreadyDumping = new AtomicBoolean(false); private boolean activated = true; private long minimalIntervalBetweenDumps = 500; private long lastDumpTime = -1; private long dumpsGenerated = 0; /** * Constructs a ThreadMonitor object to get thread information in the local JVM. */ private ThreadMonitor() { this(ManagementFactory.getPlatformMBeanServer()); } /** * Constructs a ThreadMonitor object to get thread information in a remote JVM. */ private ThreadMonitor(MBeanServerConnection server) { setMBeanServerConnection(server); try { parseMBeanInfo(); } catch (IOException e) { logger.error(e.getMessage(), e); } } /** * Retrieves the singleton instance, creating it if it doesn't exist yet. Be sure to call the shutdownInstance() * method once you no longer need it. * @return */ public static ThreadMonitor getInstance() { if (instance == null) { synchronized (ThreadMonitor.class) { if (instance == null) { instance = new ThreadMonitor(); } } } return instance; } /** * Shuts down the singleton instance, calls this when undeploying your application, or call it from the * dependency injection system upon destruction of the application context. */ public static void shutdownInstance() { if (instance == null) { return; } instance.shutdown(); instance = null; } public boolean isDebugLogging() { return debugLogging; } /** * Activate this to output concurrent call logging to System.out * @param debugLogging */ public void setDebugLogging(boolean debugLogging) { this.debugLogging = debugLogging; } public boolean isActivated() { return activated; } /** * With this method you can activate/deactivate thread dumping methods. If deactivated, the methods will simply * do nothing. * @param activated */ public void setActivated(boolean activated) { this.activated = activated; } public long getMinimalIntervalBetweenDumps() { return minimalIntervalBetweenDumps; } /** * Sets the minimal interval allowed between thread dumps. This makes it easy to avoid loading the CPU with * thread dumps under heavy load. The default value is 20ms between two thread dumps. If a thread dump is requested * before the interval has elapsed it will simply be ignored (there is no queuing). Some methods that return a * String will indicate this behavior. * @param minimalIntervalBetweenDumps specified in milliseconds (default value is 20ms). */ public void setMinimalIntervalBetweenDumps(long minimalIntervalBetweenDumps) { this.minimalIntervalBetweenDumps = minimalIntervalBetweenDumps; } public boolean isDumping() { return alreadyDumping.get(); } private void shutdown() { if (timer != null) { timer.cancel(); } } private boolean acquireAlreadyDumping() { boolean dumping = alreadyDumping.get(); if (dumping) { debug("Thread dump already in progress, ignoring..."); return true; } else { // let's check the interval since that last dump. long currentTime = System.currentTimeMillis(); if ((currentTime - lastDumpTime) <= minimalIntervalBetweenDumps) { debug("Cannot dump threads as minimal interval (" + minimalIntervalBetweenDumps + "ms) between dumps has not elapsed (=" + (currentTime - lastDumpTime) + "ms)"); return true; } else { debug("More than minimal interval (" + minimalIntervalBetweenDumps + "ms) has elapsed (=" + (currentTime - lastDumpTime) + "ms), letting dump go through..."); alreadyDumping.set(true); return false; } } } private void releaseAlreadyDumping() { lastDumpTime = System.currentTimeMillis(); dumpsGenerated++; alreadyDumping.set(false); debug("Released dumping lock, alreadyDumping=" + alreadyDumping.get()); } private void cancelTimer() { timer.cancel(); timer = null; } /** * Prints the thread dump information to System.out or/and to a file. * @param toSysOut print the generated thread dump to a System.out * @param toFile print the generated thread dump to a file * @return the file where the thread dumps will be performed into, if the dumps are done into a file */ public File dumpThreadInfo(boolean toSysOut, boolean toFile) { if (!activated) { return null; } if (!(toSysOut || toFile)) { return null; } if (acquireAlreadyDumping()) { return null; } long startTime = System.currentTimeMillis(); String threadInfo = getFullThreadInfo(); if (toSysOut) { System.out.print(threadInfo); } File dumpFile = null; if (toFile) { dumpFile = getNextThreadDumpFile(null); try { FileUtils.writeStringToFile(dumpFile, threadInfo, "UTF-8"); long dumpTime = System.currentTimeMillis() - startTime; out("Wrote thread dump to file " + dumpFile.getAbsolutePath() + " in " + dumpTime + " ms"); } catch (IOException e) { logger.error(e.getMessage(), e); } } releaseAlreadyDumping(); return dumpFile; } private void dumpThreadInfo(StringBuilder dump) { dump.append(getDumpDate()); dump.append(dumpPrefix); dump.append("\n"); long[] tids = tmbean.getAllThreadIds(); ThreadInfo[] tinfos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE); for (int i = 0; i < tinfos.length; i++) { ThreadInfo ti = tinfos[i]; if (ti != null) { printThreadInfo(ti, dump); } } dump.append(DUMP_END); } private void dumpThreadInfoUsingJstack(StringBuilder dump) { BufferedReader br = null; try { Process p = Runtime.getRuntime().exec("jstack " + JahiaContextLoaderListener.getPid()); br = new BufferedReader(new InputStreamReader(p.getInputStream())); for (String line = br.readLine(); line != null; line = br.readLine()) { dump.append(line).append("\n"); } } catch (IOException e) { logger.error(e.getMessage(), e); } finally { IOUtils.closeQuietly(br); } } /** * Starts a background thread to do series of thread dumps with the specified interval. * * @param toSysOut * print the generated thread dump to a System.out * @param toFile * print the generated thread dump to a file * @param threadDumpCount * the number of thread dumps to do * @param intervalSeconds * the interval between thread dumps in seconds * @return the file where the thread dumps will be performed into, if the dumps are done into a file */ public File dumpThreadInfoWithInterval(boolean toSysOut, boolean toFile, int threadDumpCount, int intervalSeconds) { if (!activated) { return null; } if (threadDumpCount < 1 || intervalSeconds < 1 || !(toSysOut || toFile)) { return null; } if (acquireAlreadyDumping()) { return null; } if (timer == null) { timer = new Timer("DumpThreadInfoWithInterval", true); } File file = toFile ? getNextThreadDumpFile("-" + threadDumpCount + "-executions") : null; timer.schedule(new ThreadDumpTask(threadDumpCount, toSysOut, file), 0, intervalSeconds * 1000L); return file; // releaseAlreadyDumping is done in ThreadDumpTask class. } /** * Checks if any threads are deadlocked. If any, print the thread dump information. */ public String findDeadlock() { if (!activated) { return THREAD_MONITOR_DEACTIVATED; } if (acquireAlreadyDumping()) { return "Dead lock detection already in progress in another thread, will not report"; } StringBuilder dump = new StringBuilder(); long[] tids = tmbean.findMonitorDeadlockedThreads(); if (tids == null) { releaseAlreadyDumping(); return null; } dump.append("\n\nFound one Java-level deadlock:\n"); dump.append("==============================\n"); ThreadInfo[] infos = tmbean.getThreadInfo(tids, Integer.MAX_VALUE); for (int i = 1; i < infos.length; i++) { ThreadInfo ti = infos[i]; // print thread information printThreadInfo(ti, dump); } releaseAlreadyDumping(); return (dump.toString()); } /** * Generates a string with the full thread dump information. * * @param writer the output writer */ public void generateThreadInfo(Writer writer) { if (!activated) { try { writer.write(THREAD_MONITOR_DEACTIVATED); } catch (IOException e) { logger.error(e.getMessage(), e); //To change body of catch statement use File | Settings | File Templates. } return; } if (acquireAlreadyDumping()) { try { writer.write("Thread info generation already in progress in another thread."); } catch (IOException e) { logger.error(e.getMessage(), e); //To change body of catch statement use File | Settings | File Templates. } return; } try { writer.write(getFullThreadInfo()); } catch (IOException e) { logger.error(e.getMessage(), e); } finally { releaseAlreadyDumping(); } } /** * create dump date similar to format used by 1.6 VMs * * @return dump date (e.g. 2007-10-25 08:00:00) */ private String getDumpDate() { return (DATE_FORMAT.format(new Date())); } /** * Generates a string with the full thread dump information. * * @return the thread dump content as string */ private String getFullThreadInfo() { StringBuilder dump = new StringBuilder(65536); if ((SettingsBean.getInstance() != null) && SettingsBean.getInstance().isUseJstackForThreadDumps()) { dumpThreadInfoUsingJstack(dump); } else { dumpThreadInfo(dump); } return dump.toString(); } private void parseMBeanInfo() throws IOException { setDumpPrefix(); } private void printThread(ThreadInfo ti, StringBuilder dump) { StringBuilder sb = new StringBuilder( "\"" + ti.getThreadName() + "\"" + " nid=" + ti.getThreadId() + " state=" + ti.getThreadState()); if (ti.isSuspended()) { sb.append(" (suspended)"); } if (ti.isInNative()) { sb.append(" (running in native)"); } sb.append(" []\n" + Patterns.DOLLAR.matcher(ti.getThreadState().getClass().getName()).replaceAll(".") + ": " + ti.getThreadState()); if (ti.getLockName() != null && ti.getThreadState() != Thread.State.BLOCKED) { String[] lockInfo = Patterns.AT.split(ti.getLockName()); sb.append("\n" + INDENT + "- waiting on <0x" + lockInfo[1] + "> (a " + lockInfo[0] + ")"); sb.append("\n" + INDENT + "- locked <0x" + lockInfo[1] + "> (a " + lockInfo[0] + ")"); } else if (ti.getLockName() != null && ti.getThreadState() == Thread.State.BLOCKED) { String[] lockInfo = Patterns.AT.split(ti.getLockName()); sb.append("\n" + INDENT + "- waiting to lock <0x" + lockInfo[1] + "> (a " + lockInfo[0] + ")"); } dump.append(sb.toString()); dump.append("\n"); if (ti.getLockOwnerName() != null) { dump.append(INDENT + " owned by " + ti.getLockOwnerName() + " id=" + ti.getLockOwnerId()); dump.append("\n"); } } private void printThreadInfo(ThreadInfo ti, StringBuilder dump) { // print thread information printThread(ti, dump); // print stack trace with locks StackTraceElement[] stacktrace = ti.getStackTrace(); for (int i = 0; i < stacktrace.length; i++) { StackTraceElement ste = stacktrace[i]; dump.append(INDENT + "at " + ste.toString()); dump.append("\n"); } dump.append("\n"); } private void setDumpPrefix() { RuntimeMXBean rmbean = ManagementFactory.getRuntimeMXBean(); dumpPrefix += rmbean.getVmName() + " (" + rmbean.getVmVersion() + ")\n"; } /** * reset mbean server connection * * @param mbs */ void setMBeanServerConnection(MBeanServerConnection mbs) { this.tmbean = (ThreadMXBean) ManagementFactory.getThreadMXBean(); } public long getDumpsGenerated() { return dumpsGenerated; } public long getLastDumpTime() { return lastDumpTime; } }