47
48 /**
49 * Thread dump support.
50 *
51 * This class defines static methods to support the Thread.dump_to_file diagnostic command
52 * and the HotSpotDiagnosticMXBean.dumpThreads API. It defines methods to generate a
53 * thread dump to a file or byte array in plain text or JSON format.
54 */
55 public class ThreadDumper {
56 private ThreadDumper() { }
57
58 // the maximum byte array to return when generating the thread dump to a byte array
59 private static final int MAX_BYTE_ARRAY_SIZE = 16_000;
60
61 /**
62 * Generate a thread dump in plain text format to a file or byte array, UTF-8 encoded.
63 * This method is invoked by the VM for the Thread.dump_to_file diagnostic command.
64 *
65 * @param file the file path to the file, null or "-" to return a byte array
66 * @param okayToOverwrite true to overwrite an existing file
67 * @return the UTF-8 encoded thread dump or message to return to the tool user
68 */
69 public static byte[] dumpThreads(String file, boolean okayToOverwrite) {
70 if (file == null || file.equals("-")) {
71 return dumpThreadsToByteArray(false, MAX_BYTE_ARRAY_SIZE);
72 } else {
73 return dumpThreadsToFile(file, okayToOverwrite, false);
74 }
75 }
76
77 /**
78 * Generate a thread dump in JSON format to a file or byte array, UTF-8 encoded.
79 * This method is invoked by the VM for the Thread.dump_to_file diagnostic command.
80 *
81 * @param file the file path to the file, null or "-" to return a byte array
82 * @param okayToOverwrite true to overwrite an existing file
83 * @return the UTF-8 encoded thread dump or message to return to the tool user
84 */
85 public static byte[] dumpThreadsToJson(String file, boolean okayToOverwrite) {
86 if (file == null || file.equals("-")) {
87 return dumpThreadsToByteArray(true, MAX_BYTE_ARRAY_SIZE);
88 } else {
89 return dumpThreadsToFile(file, okayToOverwrite, true);
90 }
91 }
92
93 /**
94 * Generate a thread dump in plain text or JSON format to a byte array, UTF-8 encoded.
95 * This method is the implementation of the Thread.dump_to_file diagnostic command
96 * when a file path is not specified. It returns the thread dump and/or message to
97 * send to the tool user.
98 */
99 private static byte[] dumpThreadsToByteArray(boolean json, int maxSize) {
100 var out = new BoundedByteArrayOutputStream(maxSize);
101 try (out; var writer = new TextWriter(out)) {
102 if (json) {
103 dumpThreadsToJson(writer);
104 } else {
105 dumpThreads(writer);
106 }
107 } catch (Exception ex) {
108 if (ex instanceof UncheckedIOException ioe) {
109 ex = ioe.getCause();
110 }
111 String reply = String.format("Failed: %s%n", ex);
112 return reply.getBytes(StandardCharsets.UTF_8);
113 }
114 return out.toByteArray();
115 }
116
117 /**
118 * Generate a thread dump in plain text or JSON format to the given file, UTF-8 encoded.
119 * This method is the implementation of the Thread.dump_to_file diagnostic command.
120 * It returns the thread dump and/or message to send to the tool user.
121 */
122 private static byte[] dumpThreadsToFile(String file, boolean okayToOverwrite, boolean json) {
123 Path path = Path.of(file).toAbsolutePath();
124 OpenOption[] options = (okayToOverwrite)
125 ? new OpenOption[0]
126 : new OpenOption[] { StandardOpenOption.CREATE_NEW };
127 String reply;
128 try (OutputStream out = Files.newOutputStream(path, options)) {
129 try (var writer = new TextWriter(out)) {
130 if (json) {
131 dumpThreadsToJson(writer);
132 } else {
133 dumpThreads(writer);
134 }
135 reply = String.format("Created %s%n", path);
136 } catch (UncheckedIOException e) {
137 reply = String.format("Failed: %s%n", e.getCause());
138 }
139 } catch (FileAlreadyExistsException _) {
140 reply = String.format("%s exists, use -overwrite to overwrite%n", path);
141 } catch (Exception ex) {
142 reply = String.format("Failed: %s%n", ex);
143 }
144 return reply.getBytes(StandardCharsets.UTF_8);
145 }
146
147 /**
148 * Generate a thread dump in plain text format to the given output stream, UTF-8
149 * encoded. This method is invoked by HotSpotDiagnosticMXBean.dumpThreads.
150 * @throws IOException if an I/O error occurs
151 */
163 /**
164 * Generate a thread dump in plain text format to the given text stream.
165 * @throws UncheckedIOException if an I/O error occurs
166 */
167 private static void dumpThreads(TextWriter writer) {
168 writer.println(processId());
169 writer.println(Instant.now());
170 writer.println(Runtime.version());
171 writer.println();
172 dumpThreads(ThreadContainers.root(), writer);
173 }
174
175 private static void dumpThreads(ThreadContainer container, TextWriter writer) {
176 container.threads().forEach(t -> dumpThread(t, writer));
177 container.children().forEach(c -> dumpThreads(c, writer));
178 }
179
180 private static boolean dumpThread(Thread thread, TextWriter writer) {
181 ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
182 if (snapshot == null) {
183 return false; // thread terminated
184 }
185 Instant now = Instant.now();
186 Thread.State state = snapshot.threadState();
187 writer.println("#" + thread.threadId() + " \"" + snapshot.threadName()
188 + "\" " + (thread.isVirtual() ? "virtual " : "") + state + " " + now);
189
190 StackTraceElement[] stackTrace = snapshot.stackTrace();
191 int depth = 0;
192 while (depth < stackTrace.length) {
193 writer.print(" at ");
194 writer.println(stackTrace[depth]);
195 snapshot.ownedMonitorsAt(depth).forEach(o -> {
196 if (o != null) {
197 writer.println(" - locked " + decorateObject(o));
198 } else {
199 writer.println(" - lock is eliminated");
200 }
201 });
202
203 // if parkBlocker set, or blocked/waiting on monitor, then print after top frame
225 writer.println();
226 return true;
227 }
228
229 /**
230 * Returns the identity string for the given object in a form suitable for the plain
231 * text format thread dump.
232 */
233 private static String decorateObject(Object obj) {
234 return "<" + Objects.toIdentityString(obj) + ">";
235 }
236
237 /**
238 * Generate a thread dump in JSON format to the given output stream, UTF-8 encoded.
239 * This method is invoked by HotSpotDiagnosticMXBean.dumpThreads.
240 * @throws IOException if an I/O error occurs
241 */
242 public static void dumpThreadsToJson(OutputStream out) throws IOException {
243 var writer = new TextWriter(out);
244 try {
245 dumpThreadsToJson(writer);
246 writer.flush();
247 } catch (UncheckedIOException e) {
248 IOException ioe = e.getCause();
249 throw ioe;
250 }
251 }
252
253 /**
254 * Generate a thread dump to the given text stream in JSON format.
255 * @throws UncheckedIOException if an I/O error occurs
256 */
257 private static void dumpThreadsToJson(TextWriter textWriter) {
258 var jsonWriter = new JsonWriter(textWriter);
259
260 jsonWriter.startObject(); // top-level object
261
262 jsonWriter.startObject("threadDump");
263
264 jsonWriter.writeProperty("processId", processId());
265 jsonWriter.writeProperty("time", Instant.now());
266 jsonWriter.writeProperty("runtimeVersion", Runtime.version());
267
268 jsonWriter.startArray("threadContainers");
269 dumpThreads(ThreadContainers.root(), jsonWriter);
270 jsonWriter.endArray();
271
272 jsonWriter.endObject(); // threadDump
273
274 jsonWriter.endObject(); // end of top-level object
275 }
276
277 /**
278 * Write a thread container to the given JSON writer.
301 if (!ThreadContainers.trackAllThreads()) {
302 threadCount = Long.max(threadCount, container.threadCount());
303 }
304 jsonWriter.writeProperty("threadCount", threadCount);
305
306 jsonWriter.endObject();
307
308 // the children of the thread container follow
309 container.children().forEach(c -> dumpThreads(c, jsonWriter));
310 }
311
312 /**
313 * Write a thread to the given JSON writer.
314 * @return true if the thread dump was written, false otherwise
315 * @throws UncheckedIOException if an I/O error occurs
316 */
317 private static boolean dumpThread(Thread thread, JsonWriter jsonWriter) {
318 Instant now = Instant.now();
319 ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
320 if (snapshot == null) {
321 return false; // thread terminated
322 }
323 Thread.State state = snapshot.threadState();
324 StackTraceElement[] stackTrace = snapshot.stackTrace();
325
326 jsonWriter.startObject();
327 jsonWriter.writeProperty("tid", thread.threadId());
328 jsonWriter.writeProperty("time", now);
329 if (thread.isVirtual()) {
330 jsonWriter.writeProperty("virtual", Boolean.TRUE);
331 }
332 jsonWriter.writeProperty("name", snapshot.threadName());
333 jsonWriter.writeProperty("state", state);
334
335 // park blocker
336 Object parkBlocker = snapshot.parkBlocker();
337 if (parkBlocker != null) {
338 // parkBlocker is an object to allow for exclusiveOwnerThread in the future
339 jsonWriter.startObject("parkBlocker");
340 jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker));
341 if (snapshot.parkBlockerOwner() instanceof Thread owner) {
395 private static class Node {
396 final boolean isArray;
397 int propertyCount;
398 Node(boolean isArray) {
399 this.isArray = isArray;
400 }
401 boolean isArray() {
402 return isArray;
403 }
404 int propertyCount() {
405 return propertyCount;
406 }
407 int getAndIncrementPropertyCount() {
408 int old = propertyCount;
409 propertyCount++;
410 return old;
411 }
412 }
413 private final Deque<Node> stack = new ArrayDeque<>();
414 private final TextWriter writer;
415
416 JsonWriter(TextWriter writer) {
417 this.writer = writer;
418 }
419
420 private void indent() {
421 int indent = stack.size() * 2;
422 writer.print(" ".repeat(indent));
423 }
424
425 /**
426 * Start of object or array.
427 */
428 private void startObject(String name, boolean isArray) {
429 if (!stack.isEmpty()) {
430 Node node = stack.peek();
431 if (node.getAndIncrementPropertyCount() > 0) {
432 writer.println(",");
433 }
434 }
435 indent();
436 if (name != null) {
437 writer.print("\"" + name + "\": ");
438 }
439 writer.println(isArray ? "[" : "{");
440 stack.push(new Node(isArray));
441 }
442
443 /**
444 * End of object or array.
445 */
446 private void endObject(boolean isArray) {
447 Node node = stack.pop();
448 if (node.isArray() != isArray)
449 throw new IllegalStateException();
450 if (node.propertyCount() > 0) {
451 writer.println();
452 }
453 indent();
454 writer.print(isArray ? "]" : "}");
455 }
456
457 /**
458 * Write a property.
459 * @param name the property name, null for an unnamed property
460 * @param obj the value or null
461 */
462 void writeProperty(String name, Object obj) {
463 Node node = stack.peek();
464 if (node.getAndIncrementPropertyCount() > 0) {
465 writer.println(",");
466 }
467 indent();
468 if (name != null) {
469 writer.print("\"" + name + "\": ");
470 }
471 switch (obj) {
472 // Long may be larger than safe range of JSON integer value
473 case Long _ -> writer.print("\"" + obj + "\"");
474 case Number _ -> writer.print(obj);
475 case Boolean _ -> writer.print(obj);
476 case null -> writer.print("null");
477 default -> writer.print("\"" + escape(obj.toString()) + "\"");
478 }
479 }
480
481 /**
482 * Write an unnamed property.
483 */
484 void writeProperty(Object obj) {
485 writeProperty(null, obj);
486 }
487
488 /**
489 * Start named object.
490 */
491 void startObject(String name) {
492 startObject(name, false);
493 }
494
495 /**
496 * Start unnamed object.
497 */
|
47
48 /**
49 * Thread dump support.
50 *
51 * This class defines static methods to support the Thread.dump_to_file diagnostic command
52 * and the HotSpotDiagnosticMXBean.dumpThreads API. It defines methods to generate a
53 * thread dump to a file or byte array in plain text or JSON format.
54 */
55 public class ThreadDumper {
56 private ThreadDumper() { }
57
58 // the maximum byte array to return when generating the thread dump to a byte array
59 private static final int MAX_BYTE_ARRAY_SIZE = 16_000;
60
61 /**
62 * Generate a thread dump in plain text format to a file or byte array, UTF-8 encoded.
63 * This method is invoked by the VM for the Thread.dump_to_file diagnostic command.
64 *
65 * @param file the file path to the file, null or "-" to return a byte array
66 * @param okayToOverwrite true to overwrite an existing file
67 * @param ignore not used
68 * @return the UTF-8 encoded thread dump or message to return to the tool user
69 */
70 public static byte[] dumpThreads(String file, boolean okayToOverwrite, boolean ignore) {
71 if (file == null || file.equals("-")) {
72 return dumpThreadsToByteArray(false, false, MAX_BYTE_ARRAY_SIZE);
73 } else {
74 return dumpThreadsToFile(file, okayToOverwrite, false, false);
75 }
76 }
77
78 /**
79 * Generate a thread dump in JSON format to a file or byte array, UTF-8 encoded.
80 * This method is invoked by the VM for the Thread.dump_to_file diagnostic command.
81 *
82 * @param file the file path to the file, null or "-" to return a byte array
83 * @param okayToOverwrite true to overwrite an existing file
84 * @return the UTF-8 encoded thread dump or message to return to the tool user
85 */
86 public static byte[] dumpThreadsToJson(String file, boolean okayToOverwrite, boolean minify) {
87 if (file == null || file.equals("-")) {
88 return dumpThreadsToByteArray(true, !minify, MAX_BYTE_ARRAY_SIZE);
89 } else {
90 return dumpThreadsToFile(file, okayToOverwrite, true, minify);
91 }
92 }
93
94 /**
95 * Generate a thread dump in plain text or JSON format to a byte array, UTF-8 encoded.
96 * This method is the implementation of the Thread.dump_to_file diagnostic command
97 * when a file path is not specified. It returns the thread dump and/or message to
98 * send to the tool user.
99 */
100 private static byte[] dumpThreadsToByteArray(boolean json, boolean minify, int maxSize) {
101 var out = new BoundedByteArrayOutputStream(maxSize);
102 try (out; var writer = new TextWriter(out)) {
103 if (json) {
104 dumpThreadsToJson(writer, minify);
105 } else {
106 dumpThreads(writer);
107 }
108 } catch (Exception ex) {
109 if (ex instanceof UncheckedIOException ioe) {
110 ex = ioe.getCause();
111 }
112 String reply = String.format("Failed: %s%n", ex);
113 return reply.getBytes(StandardCharsets.UTF_8);
114 }
115 return out.toByteArray();
116 }
117
118 /**
119 * Generate a thread dump in plain text or JSON format to the given file, UTF-8 encoded.
120 * This method is the implementation of the Thread.dump_to_file diagnostic command.
121 * It returns the thread dump and/or message to send to the tool user.
122 */
123 private static byte[] dumpThreadsToFile(String file,
124 boolean okayToOverwrite,
125 boolean json,
126 boolean minify) {
127 Path path = Path.of(file).toAbsolutePath();
128 OpenOption[] options = (okayToOverwrite)
129 ? new OpenOption[0]
130 : new OpenOption[] { StandardOpenOption.CREATE_NEW };
131 String reply;
132 try (OutputStream out = Files.newOutputStream(path, options)) {
133 try (var writer = new TextWriter(out)) {
134 if (json) {
135 dumpThreadsToJson(writer, minify);
136 } else {
137 dumpThreads(writer);
138 }
139 reply = String.format("Created %s%n", path);
140 } catch (UncheckedIOException e) {
141 reply = String.format("Failed: %s%n", e.getCause());
142 }
143 } catch (FileAlreadyExistsException _) {
144 reply = String.format("%s exists, use -overwrite to overwrite%n", path);
145 } catch (Exception ex) {
146 reply = String.format("Failed: %s%n", ex);
147 }
148 return reply.getBytes(StandardCharsets.UTF_8);
149 }
150
151 /**
152 * Generate a thread dump in plain text format to the given output stream, UTF-8
153 * encoded. This method is invoked by HotSpotDiagnosticMXBean.dumpThreads.
154 * @throws IOException if an I/O error occurs
155 */
167 /**
168 * Generate a thread dump in plain text format to the given text stream.
169 * @throws UncheckedIOException if an I/O error occurs
170 */
171 private static void dumpThreads(TextWriter writer) {
172 writer.println(processId());
173 writer.println(Instant.now());
174 writer.println(Runtime.version());
175 writer.println();
176 dumpThreads(ThreadContainers.root(), writer);
177 }
178
179 private static void dumpThreads(ThreadContainer container, TextWriter writer) {
180 container.threads().forEach(t -> dumpThread(t, writer));
181 container.children().forEach(c -> dumpThreads(c, writer));
182 }
183
184 private static boolean dumpThread(Thread thread, TextWriter writer) {
185 ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
186 if (snapshot == null) {
187 return false; // thread not alive
188 }
189 Instant now = Instant.now();
190 Thread.State state = snapshot.threadState();
191 writer.println("#" + thread.threadId() + " \"" + snapshot.threadName()
192 + "\" " + (thread.isVirtual() ? "virtual " : "") + state + " " + now);
193
194 StackTraceElement[] stackTrace = snapshot.stackTrace();
195 int depth = 0;
196 while (depth < stackTrace.length) {
197 writer.print(" at ");
198 writer.println(stackTrace[depth]);
199 snapshot.ownedMonitorsAt(depth).forEach(o -> {
200 if (o != null) {
201 writer.println(" - locked " + decorateObject(o));
202 } else {
203 writer.println(" - lock is eliminated");
204 }
205 });
206
207 // if parkBlocker set, or blocked/waiting on monitor, then print after top frame
229 writer.println();
230 return true;
231 }
232
233 /**
234 * Returns the identity string for the given object in a form suitable for the plain
235 * text format thread dump.
236 */
237 private static String decorateObject(Object obj) {
238 return "<" + Objects.toIdentityString(obj) + ">";
239 }
240
241 /**
242 * Generate a thread dump in JSON format to the given output stream, UTF-8 encoded.
243 * This method is invoked by HotSpotDiagnosticMXBean.dumpThreads.
244 * @throws IOException if an I/O error occurs
245 */
246 public static void dumpThreadsToJson(OutputStream out) throws IOException {
247 var writer = new TextWriter(out);
248 try {
249 dumpThreadsToJson(writer, /*prettyPrint*/ true);
250 writer.flush();
251 } catch (UncheckedIOException e) {
252 IOException ioe = e.getCause();
253 throw ioe;
254 }
255 }
256
257 /**
258 * Generate a thread dump to the given text stream in JSON format.
259 * @throws UncheckedIOException if an I/O error occurs
260 */
261 private static void dumpThreadsToJson(TextWriter textWriter, boolean minify) {
262 var jsonWriter = new JsonWriter(textWriter, minify);
263
264 jsonWriter.startObject(); // top-level object
265
266 jsonWriter.startObject("threadDump");
267
268 jsonWriter.writeProperty("processId", processId());
269 jsonWriter.writeProperty("time", Instant.now());
270 jsonWriter.writeProperty("runtimeVersion", Runtime.version());
271
272 jsonWriter.startArray("threadContainers");
273 dumpThreads(ThreadContainers.root(), jsonWriter);
274 jsonWriter.endArray();
275
276 jsonWriter.endObject(); // threadDump
277
278 jsonWriter.endObject(); // end of top-level object
279 }
280
281 /**
282 * Write a thread container to the given JSON writer.
305 if (!ThreadContainers.trackAllThreads()) {
306 threadCount = Long.max(threadCount, container.threadCount());
307 }
308 jsonWriter.writeProperty("threadCount", threadCount);
309
310 jsonWriter.endObject();
311
312 // the children of the thread container follow
313 container.children().forEach(c -> dumpThreads(c, jsonWriter));
314 }
315
316 /**
317 * Write a thread to the given JSON writer.
318 * @return true if the thread dump was written, false otherwise
319 * @throws UncheckedIOException if an I/O error occurs
320 */
321 private static boolean dumpThread(Thread thread, JsonWriter jsonWriter) {
322 Instant now = Instant.now();
323 ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
324 if (snapshot == null) {
325 return false; // thread not alive
326 }
327 Thread.State state = snapshot.threadState();
328 StackTraceElement[] stackTrace = snapshot.stackTrace();
329
330 jsonWriter.startObject();
331 jsonWriter.writeProperty("tid", thread.threadId());
332 jsonWriter.writeProperty("time", now);
333 if (thread.isVirtual()) {
334 jsonWriter.writeProperty("virtual", Boolean.TRUE);
335 }
336 jsonWriter.writeProperty("name", snapshot.threadName());
337 jsonWriter.writeProperty("state", state);
338
339 // park blocker
340 Object parkBlocker = snapshot.parkBlocker();
341 if (parkBlocker != null) {
342 // parkBlocker is an object to allow for exclusiveOwnerThread in the future
343 jsonWriter.startObject("parkBlocker");
344 jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker));
345 if (snapshot.parkBlockerOwner() instanceof Thread owner) {
399 private static class Node {
400 final boolean isArray;
401 int propertyCount;
402 Node(boolean isArray) {
403 this.isArray = isArray;
404 }
405 boolean isArray() {
406 return isArray;
407 }
408 int propertyCount() {
409 return propertyCount;
410 }
411 int getAndIncrementPropertyCount() {
412 int old = propertyCount;
413 propertyCount++;
414 return old;
415 }
416 }
417 private final Deque<Node> stack = new ArrayDeque<>();
418 private final TextWriter writer;
419 private final boolean prettyPrint; // pretty print or minify
420
421 JsonWriter(TextWriter writer, boolean minify) {
422 this.writer = writer;
423 this.prettyPrint = !minify;
424 }
425
426 private void print(Object obj) {
427 writer.print(obj);
428 }
429
430 private void println(Object obj) {
431 if (prettyPrint) {
432 writer.println(obj);
433 } else {
434 writer.print(obj);
435 }
436 }
437
438 private void println() {
439 if (prettyPrint) {
440 writer.println();
441 }
442 }
443
444 private void indent() {
445 if (prettyPrint) {
446 int indent = stack.size() * 2;
447 writer.print(" ".repeat(indent));
448 }
449 }
450
451 /**
452 * Start of object or array.
453 */
454 private void startObject(String name, boolean isArray) {
455 if (!stack.isEmpty()) {
456 Node node = stack.peek();
457 if (node.getAndIncrementPropertyCount() > 0) {
458 println(",");
459 }
460 }
461 indent();
462 if (name != null) {
463 String gap = prettyPrint ? " " : "";
464 print("\"" + name + "\":" + gap);
465 }
466 println(isArray ? "[" : "{");
467 stack.push(new Node(isArray));
468 }
469
470 /**
471 * End of object or array.
472 */
473 private void endObject(boolean isArray) {
474 Node node = stack.pop();
475 if (node.isArray() != isArray)
476 throw new IllegalStateException();
477 if (node.propertyCount() > 0) {
478 println();
479 }
480 indent();
481 print(isArray ? "]" : "}");
482 }
483
484 /**
485 * Write a property.
486 * @param name the property name, null for an unnamed property
487 * @param obj the value or null
488 */
489 void writeProperty(String name, Object obj) {
490 Node node = stack.peek();
491 assert node != null;
492 if (node.getAndIncrementPropertyCount() > 0) {
493 println(",");
494 }
495 indent();
496 if (name != null) {
497 print("\"" + name + "\": ");
498 }
499 switch (obj) {
500 // Long may be larger than safe range of JSON integer value
501 case Long _ -> print("\"" + obj + "\"");
502 case Number _ -> print(obj);
503 case Boolean _ -> print(obj);
504 case null -> print("null");
505 default -> print("\"" + escape(obj.toString()) + "\"");
506 }
507 }
508
509 /**
510 * Write an unnamed property.
511 */
512 void writeProperty(Object obj) {
513 writeProperty(null, obj);
514 }
515
516 /**
517 * Start named object.
518 */
519 void startObject(String name) {
520 startObject(name, false);
521 }
522
523 /**
524 * Start unnamed object.
525 */
|