< prev index next > src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java
Print this page
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.ArrayList;
+ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+ import java.util.Objects;
/**
* Thread dump support.
*
* This class defines methods to dump threads to an output stream or file in plain
container.threads().forEach(t -> dumpThread(t, ps));
container.children().forEach(c -> dumpThreads(c, ps));
}
private static void dumpThread(Thread thread, PrintStream ps) {
- String suffix = thread.isVirtual() ? " virtual" : "";
- ps.println("#" + thread.threadId() + " \"" + thread.getName() + "\"" + suffix);
- for (StackTraceElement ste : thread.getStackTrace()) {
+ ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
+ Thread.State state = snapshot.threadState();
+ ps.println("#" + thread.threadId() + " \"" + snapshot.threadName()
+ + "\" " + state + " " + Instant.now());
+
+ // park blocker
+ Object parkBlocker = snapshot.parkBlocker();
+ if (parkBlocker != null) {
+ ps.println(" // parked on " + Objects.toIdentityString(parkBlocker));
+ }
+
+ // blocked on monitor enter or Object.wait
+ if (state == Thread.State.BLOCKED) {
+ Object obj = snapshot.blockedOn();
+ if (obj != null) {
+ ps.println(" // blocked on " + Objects.toIdentityString(obj));
+ }
+ } else if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
+ Object obj = snapshot.waitingOn();
+ if (obj != null) {
+ ps.println(" // waiting on " + Objects.toIdentityString(obj));
+ }
+ }
+
+ StackTraceElement[] stackTrace = snapshot.stackTrace();
+ int depth = 0;
+ while (depth < stackTrace.length) {
+ snapshot.ownedMonitorsAt(depth).forEach(obj -> {
+ ps.print(" // locked ");
+ ps.println(Objects.toIdentityString(obj));
+ });
ps.print(" ");
- ps.println(ste);
+ ps.println(stackTrace[depth]);
+ depth++;
}
ps.println();
}
/**
/**
* Generate a thread dump to the given print stream in JSON format.
*/
private static void dumpThreadsToJson(PrintStream out) {
- out.println("{");
- out.println(" \"threadDump\": {");
+ try (JsonWriter jsonWriter = JsonWriter.wrap(out)) {
+ jsonWriter.startObject("threadDump");
- String now = Instant.now().toString();
- String runtimeVersion = Runtime.version().toString();
- out.format(" \"processId\": \"%d\",%n", processId());
- out.format(" \"time\": \"%s\",%n", escape(now));
- out.format(" \"runtimeVersion\": \"%s\",%n", escape(runtimeVersion));
-
- out.println(" \"threadContainers\": [");
- List<ThreadContainer> containers = allContainers();
- Iterator<ThreadContainer> iterator = containers.iterator();
- while (iterator.hasNext()) {
- ThreadContainer container = iterator.next();
- boolean more = iterator.hasNext();
- dumpThreadsToJson(container, out, more);
- }
- out.println(" ]"); // end of threadContainers
+ jsonWriter.writeProperty("processId", processId());
+ jsonWriter.writeProperty("time", Instant.now());
+ jsonWriter.writeProperty("runtimeVersion", Runtime.version());
- out.println(" }"); // end threadDump
- out.println("}"); // end object
+ jsonWriter.startArray("threadContainers");
+ allContainers().forEach(c -> dumpThreadsToJson(c, jsonWriter));
+ jsonWriter.endArray();
+
+ jsonWriter.endObject(); // threadDump
+ }
}
/**
- * Dump the given thread container to the print stream in JSON format.
+ * Write a thread container to the given JSON writer.
*/
- private static void dumpThreadsToJson(ThreadContainer container,
- PrintStream out,
- boolean more) {
- out.println(" {");
- out.format(" \"container\": \"%s\",%n", escape(container.toString()));
-
- ThreadContainer parent = container.parent();
- if (parent == null) {
- out.format(" \"parent\": null,%n");
- } else {
- out.format(" \"parent\": \"%s\",%n", escape(parent.toString()));
- }
+ private static void dumpThreadsToJson(ThreadContainer container, JsonWriter jsonWriter) {
+ jsonWriter.startObject();
+ jsonWriter.writeProperty("container", container);
+ jsonWriter.writeProperty("parent", container.parent());
Thread owner = container.owner();
- if (owner == null) {
- out.format(" \"owner\": null,%n");
- } else {
- out.format(" \"owner\": \"%d\",%n", owner.threadId());
- }
+ jsonWriter.writeProperty("owner", (owner != null) ? owner.threadId() : null);
long threadCount = 0;
- out.println(" \"threads\": [");
+ jsonWriter.startArray("threads");
Iterator<Thread> threads = container.threads().iterator();
while (threads.hasNext()) {
Thread thread = threads.next();
- dumpThreadToJson(thread, out, threads.hasNext());
+ dumpThreadToJson(thread, jsonWriter);
threadCount++;
}
- out.println(" ],"); // end of threads
+ jsonWriter.endArray(); // threads
// thread count
if (!ThreadContainers.trackAllThreads()) {
threadCount = Long.max(threadCount, container.threadCount());
}
- out.format(" \"threadCount\": \"%d\"%n", threadCount);
+ jsonWriter.writeProperty("threadCount", threadCount);
- if (more) {
- out.println(" },");
- } else {
- out.println(" }"); // last container, no trailing comma
- }
+ jsonWriter.endObject();
}
/**
- * Dump the given thread and its stack trace to the print stream in JSON format.
+ * Write a thread to the given JSON writer.
*/
- private static void dumpThreadToJson(Thread thread, PrintStream out, boolean more) {
- out.println(" {");
- out.println(" \"tid\": \"" + thread.threadId() + "\",");
- out.println(" \"name\": \"" + escape(thread.getName()) + "\",");
- out.println(" \"stack\": [");
-
- int i = 0;
- StackTraceElement[] stackTrace = thread.getStackTrace();
- while (i < stackTrace.length) {
- out.print(" \"");
- out.print(escape(stackTrace[i].toString()));
- out.print("\"");
- i++;
- if (i < stackTrace.length) {
- out.println(",");
- } else {
- out.println(); // last element, no trailing comma
+ private static void dumpThreadToJson(Thread thread, JsonWriter jsonWriter) {
+ String now = Instant.now().toString();
+ ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
+ Thread.State state = snapshot.threadState();
+ StackTraceElement[] stackTrace = snapshot.stackTrace();
+
+ jsonWriter.startObject();
+ jsonWriter.writeProperty("tid", thread.threadId());
+ jsonWriter.writeProperty("time", now);
+ jsonWriter.writeProperty("name", snapshot.threadName());
+ jsonWriter.writeProperty("state", state);
+
+ // park blocker
+ Object parkBlocker = snapshot.parkBlocker();
+ if (parkBlocker != null) {
+ jsonWriter.writeProperty("parkBlocker", Objects.toIdentityString(parkBlocker));
+ }
+
+ // blocked on monitor enter or Object.wait
+ if (state == Thread.State.BLOCKED) {
+ Object obj = snapshot.blockedOn();
+ if (obj != null) {
+ jsonWriter.writeProperty("blockedOn", Objects.toIdentityString(obj));
+ }
+ } else if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
+ Object obj = snapshot.waitingOn();
+ if (obj != null) {
+ jsonWriter.writeProperty("waitingOn", Objects.toIdentityString(obj));
}
}
- out.println(" ]");
- if (more) {
- out.println(" },");
- } else {
- out.println(" }"); // last thread, no trailing comma
+
+ // stack trace
+ jsonWriter.startArray("stack");
+ Arrays.stream(stackTrace).forEach(jsonWriter::writeProperty);
+ jsonWriter.endArray();
+
+ // monitors owned, skip if none
+ if (snapshot.ownsMonitors()) {
+ jsonWriter.startArray("monitorsOwned");
+ int depth = 0;
+ while (depth < stackTrace.length) {
+ List<Object> objs = snapshot.ownedMonitorsAt(depth).toList();
+ if (!objs.isEmpty()) {
+ jsonWriter.startObject();
+ jsonWriter.writeProperty("depth", depth);
+ jsonWriter.startArray("locks");
+ snapshot.ownedMonitorsAt(depth)
+ .map(Objects::toIdentityString)
+ .forEach(jsonWriter::writeProperty);
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ }
+ depth++;
+ }
+ jsonWriter.endArray();
}
+
+ jsonWriter.endObject();
}
/**
* Returns a list of all thread containers that are "reachable" from
* the root container.
containers.add(container);
container.children().forEach(c -> collect(c, containers));
}
/**
- * Escape any characters that need to be escape in the JSON output.
+ * Simple JSON writer to stream objects/arrays to a PrintStream.
*/
- private static String escape(String value) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < value.length(); i++) {
- char c = value.charAt(i);
- switch (c) {
- case '"' -> sb.append("\\\"");
- case '\\' -> sb.append("\\\\");
- case '/' -> sb.append("\\/");
- case '\b' -> sb.append("\\b");
- case '\f' -> sb.append("\\f");
- case '\n' -> sb.append("\\n");
- case '\r' -> sb.append("\\r");
- case '\t' -> sb.append("\\t");
- default -> {
- if (c <= 0x1f) {
- sb.append(String.format("\\u%04x", c));
- } else {
- sb.append(c);
+ private static class JsonWriter implements AutoCloseable {
+ private final PrintStream out;
+
+ // current depth and indentation
+ private int depth = -1;
+ private int indent;
+
+ // indicates if there are properties at depth N
+ private boolean[] hasProperties = new boolean[10];
+
+ private JsonWriter(PrintStream out) {
+ this.out = out;
+ }
+
+ static JsonWriter wrap(PrintStream out) {
+ var writer = new JsonWriter(out);
+ writer.startObject();
+ return writer;
+ }
+
+ /**
+ * Start of object or array.
+ */
+ private void startObject(String name, boolean array) {
+ if (depth >= 0) {
+ if (hasProperties[depth]) {
+ out.println(",");
+ } else {
+ hasProperties[depth] = true; // first property at this depth
+ }
+ }
+ out.print(" ".repeat(indent));
+ if (name != null) {
+ out.print("\"" + name + "\": ");
+ }
+ if (array) {
+ out.println("[");
+ } else {
+ out.println("{");
+ }
+ indent += 2;
+ depth++;
+ hasProperties[depth] = false;
+ }
+
+ /**
+ * End of object or array.
+ */
+ private void endObject(boolean array) {
+ if (hasProperties[depth]) {
+ out.println();
+ hasProperties[depth] = false;
+ }
+ depth--;
+ indent -= 2;
+ out.print(" ".repeat(indent));
+ if (array) {
+ out.print("]");
+ } else {
+ out.print("}");
+ }
+ }
+
+ /**
+ * Write a named property.
+ */
+ void writeProperty(String name, Object obj) {
+ if (hasProperties[depth]) {
+ out.println(",");
+ } else {
+ hasProperties[depth] = true;
+ }
+ out.print(" ".repeat(indent));
+ if (name != null) {
+ out.print("\"" + name + "\": ");
+ }
+ if (obj != null) {
+ out.print("\"" + escape(obj.toString()) + "\"");
+ } else {
+ out.print("null");
+ }
+ }
+
+ /**
+ * Write an unnamed property.
+ */
+ void writeProperty(Object obj) {
+ writeProperty(null, obj);
+ }
+
+ /**
+ * Start named object.
+ */
+ void startObject(String name) {
+ startObject(name, false);
+ }
+
+ /**
+ * Start unnamed object.
+ */
+ void startObject() {
+ startObject(null);
+ }
+
+ /**
+ * End of object.
+ */
+ void endObject() {
+ endObject(false);
+ }
+
+ /**
+ * Start named array.
+ */
+ void startArray(String name) {
+ startObject(name, true);
+ }
+
+ /**
+ * End of array.
+ */
+ void endArray() {
+ endObject(true);
+ }
+
+ @Override
+ public void close() {
+ endObject();
+ out.flush();
+ }
+
+ /**
+ * Escape any characters that need to be escape in the JSON output.
+ */
+ private static String escape(String value) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ switch (c) {
+ case '"' -> sb.append("\\\"");
+ case '\\' -> sb.append("\\\\");
+ case '/' -> sb.append("\\/");
+ case '\b' -> sb.append("\\b");
+ case '\f' -> sb.append("\\f");
+ case '\n' -> sb.append("\\n");
+ case '\r' -> sb.append("\\r");
+ case '\t' -> sb.append("\\t");
+ default -> {
+ if (c <= 0x1f) {
+ sb.append(String.format("\\u%04x", c));
+ } else {
+ sb.append(c);
+ }
}
}
}
+ return sb.toString();
}
- return sb.toString();
}
/**
* A ByteArrayOutputStream of bounded size. Once the maximum number of bytes is
* written the subsequent bytes are discarded.
< prev index next >