20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package jdk.internal.vm;
26
27 import java.io.BufferedOutputStream;
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.io.PrintStream;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.FileAlreadyExistsException;
34 import java.nio.file.Files;
35 import java.nio.file.OpenOption;
36 import java.nio.file.Path;
37 import java.nio.file.StandardOpenOption;
38 import java.time.Instant;
39 import java.util.ArrayList;
40 import java.util.Iterator;
41 import java.util.List;
42
43 /**
44 * Thread dump support.
45 *
46 * This class defines methods to dump threads to an output stream or file in plain
47 * text or JSON format.
48 */
49 public class ThreadDumper {
50 private ThreadDumper() { }
51
52 // the maximum byte array to return when generating the thread dump to a byte array
53 private static final int MAX_BYTE_ARRAY_SIZE = 16_000;
54
55 /**
56 * Generate a thread dump in plain text format to a byte array or file, UTF-8 encoded.
57 *
58 * This method is invoked by the VM for the Thread.dump_to_file diagnostic command.
59 *
60 * @param file the file path to the file, null or "-" to return a byte array
61 * @param okayToOverwrite true to overwrite an existing file
143 }
144 }
145
146 /**
147 * Generate a thread dump in plain text format to the given print stream.
148 */
149 private static void dumpThreads(PrintStream ps) {
150 ps.println(processId());
151 ps.println(Instant.now());
152 ps.println(Runtime.version());
153 ps.println();
154 dumpThreads(ThreadContainers.root(), ps);
155 }
156
157 private static void dumpThreads(ThreadContainer container, PrintStream ps) {
158 container.threads().forEach(t -> dumpThread(t, ps));
159 container.children().forEach(c -> dumpThreads(c, ps));
160 }
161
162 private static void dumpThread(Thread thread, PrintStream ps) {
163 String suffix = thread.isVirtual() ? " virtual" : "";
164 ps.println("#" + thread.threadId() + " \"" + thread.getName() + "\"" + suffix);
165 for (StackTraceElement ste : thread.getStackTrace()) {
166 ps.print(" ");
167 ps.println(ste);
168 }
169 ps.println();
170 }
171
172 /**
173 * Generate a thread dump in JSON format to the given output stream, UTF-8 encoded.
174 *
175 * This method is invoked by HotSpotDiagnosticMXBean.dumpThreads.
176 */
177 public static void dumpThreadsToJson(OutputStream out) {
178 BufferedOutputStream bos = new BufferedOutputStream(out);
179 PrintStream ps = new PrintStream(bos, false, StandardCharsets.UTF_8);
180 try {
181 dumpThreadsToJson(ps);
182 } finally {
183 ps.flush(); // flushes underlying stream
184 }
185 }
186
187 /**
188 * Generate a thread dump to the given print stream in JSON format.
189 */
190 private static void dumpThreadsToJson(PrintStream out) {
191 out.println("{");
192 out.println(" \"threadDump\": {");
193
194 String now = Instant.now().toString();
195 String runtimeVersion = Runtime.version().toString();
196 out.format(" \"processId\": \"%d\",%n", processId());
197 out.format(" \"time\": \"%s\",%n", escape(now));
198 out.format(" \"runtimeVersion\": \"%s\",%n", escape(runtimeVersion));
199
200 out.println(" \"threadContainers\": [");
201 List<ThreadContainer> containers = allContainers();
202 Iterator<ThreadContainer> iterator = containers.iterator();
203 while (iterator.hasNext()) {
204 ThreadContainer container = iterator.next();
205 boolean more = iterator.hasNext();
206 dumpThreadsToJson(container, out, more);
207 }
208 out.println(" ]"); // end of threadContainers
209
210 out.println(" }"); // end threadDump
211 out.println("}"); // end object
212 }
213
214 /**
215 * Dump the given thread container to the print stream in JSON format.
216 */
217 private static void dumpThreadsToJson(ThreadContainer container,
218 PrintStream out,
219 boolean more) {
220 out.println(" {");
221 out.format(" \"container\": \"%s\",%n", escape(container.toString()));
222
223 ThreadContainer parent = container.parent();
224 if (parent == null) {
225 out.format(" \"parent\": null,%n");
226 } else {
227 out.format(" \"parent\": \"%s\",%n", escape(parent.toString()));
228 }
229
230 Thread owner = container.owner();
231 if (owner == null) {
232 out.format(" \"owner\": null,%n");
233 } else {
234 out.format(" \"owner\": \"%d\",%n", owner.threadId());
235 }
236
237 long threadCount = 0;
238 out.println(" \"threads\": [");
239 Iterator<Thread> threads = container.threads().iterator();
240 while (threads.hasNext()) {
241 Thread thread = threads.next();
242 dumpThreadToJson(thread, out, threads.hasNext());
243 threadCount++;
244 }
245 out.println(" ],"); // end of threads
246
247 // thread count
248 if (!ThreadContainers.trackAllThreads()) {
249 threadCount = Long.max(threadCount, container.threadCount());
250 }
251 out.format(" \"threadCount\": \"%d\"%n", threadCount);
252
253 if (more) {
254 out.println(" },");
255 } else {
256 out.println(" }"); // last container, no trailing comma
257 }
258 }
259
260 /**
261 * Dump the given thread and its stack trace to the print stream in JSON format.
262 */
263 private static void dumpThreadToJson(Thread thread, PrintStream out, boolean more) {
264 out.println(" {");
265 out.println(" \"tid\": \"" + thread.threadId() + "\",");
266 out.println(" \"name\": \"" + escape(thread.getName()) + "\",");
267 out.println(" \"stack\": [");
268
269 int i = 0;
270 StackTraceElement[] stackTrace = thread.getStackTrace();
271 while (i < stackTrace.length) {
272 out.print(" \"");
273 out.print(escape(stackTrace[i].toString()));
274 out.print("\"");
275 i++;
276 if (i < stackTrace.length) {
277 out.println(",");
278 } else {
279 out.println(); // last element, no trailing comma
280 }
281 }
282 out.println(" ]");
283 if (more) {
284 out.println(" },");
285 } else {
286 out.println(" }"); // last thread, no trailing comma
287 }
288 }
289
290 /**
291 * Returns a list of all thread containers that are "reachable" from
292 * the root container.
293 */
294 private static List<ThreadContainer> allContainers() {
295 List<ThreadContainer> containers = new ArrayList<>();
296 collect(ThreadContainers.root(), containers);
297 return containers;
298 }
299
300 private static void collect(ThreadContainer container, List<ThreadContainer> containers) {
301 containers.add(container);
302 container.children().forEach(c -> collect(c, containers));
303 }
304
305 /**
306 * Escape any characters that need to be escape in the JSON output.
307 */
308 private static String escape(String value) {
309 StringBuilder sb = new StringBuilder();
310 for (int i = 0; i < value.length(); i++) {
311 char c = value.charAt(i);
312 switch (c) {
313 case '"' -> sb.append("\\\"");
314 case '\\' -> sb.append("\\\\");
315 case '/' -> sb.append("\\/");
316 case '\b' -> sb.append("\\b");
317 case '\f' -> sb.append("\\f");
318 case '\n' -> sb.append("\\n");
319 case '\r' -> sb.append("\\r");
320 case '\t' -> sb.append("\\t");
321 default -> {
322 if (c <= 0x1f) {
323 sb.append(String.format("\\u%04x", c));
324 } else {
325 sb.append(c);
326 }
327 }
328 }
329 }
330 return sb.toString();
331 }
332
333 /**
334 * A ByteArrayOutputStream of bounded size. Once the maximum number of bytes is
335 * written the subsequent bytes are discarded.
336 */
337 private static class BoundedByteArrayOutputStream extends ByteArrayOutputStream {
338 final int max;
339 BoundedByteArrayOutputStream(int max) {
340 this.max = max;
341 }
342 @Override
343 public void write(int b) {
344 if (max < count) {
345 super.write(b);
346 }
347 }
348 @Override
349 public void write(byte[] b, int off, int len) {
350 int remaining = max - count;
|
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package jdk.internal.vm;
26
27 import java.io.BufferedOutputStream;
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.io.PrintStream;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.FileAlreadyExistsException;
34 import java.nio.file.Files;
35 import java.nio.file.OpenOption;
36 import java.nio.file.Path;
37 import java.nio.file.StandardOpenOption;
38 import java.time.Instant;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Objects;
44
45 /**
46 * Thread dump support.
47 *
48 * This class defines methods to dump threads to an output stream or file in plain
49 * text or JSON format.
50 */
51 public class ThreadDumper {
52 private ThreadDumper() { }
53
54 // the maximum byte array to return when generating the thread dump to a byte array
55 private static final int MAX_BYTE_ARRAY_SIZE = 16_000;
56
57 /**
58 * Generate a thread dump in plain text format to a byte array or file, UTF-8 encoded.
59 *
60 * This method is invoked by the VM for the Thread.dump_to_file diagnostic command.
61 *
62 * @param file the file path to the file, null or "-" to return a byte array
63 * @param okayToOverwrite true to overwrite an existing file
145 }
146 }
147
148 /**
149 * Generate a thread dump in plain text format to the given print stream.
150 */
151 private static void dumpThreads(PrintStream ps) {
152 ps.println(processId());
153 ps.println(Instant.now());
154 ps.println(Runtime.version());
155 ps.println();
156 dumpThreads(ThreadContainers.root(), ps);
157 }
158
159 private static void dumpThreads(ThreadContainer container, PrintStream ps) {
160 container.threads().forEach(t -> dumpThread(t, ps));
161 container.children().forEach(c -> dumpThreads(c, ps));
162 }
163
164 private static void dumpThread(Thread thread, PrintStream ps) {
165 ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
166 Thread.State state = snapshot.threadState();
167 ps.println("#" + thread.threadId() + " \"" + snapshot.threadName()
168 + "\" " + state + " " + Instant.now());
169
170 // park blocker
171 Object parkBlocker = snapshot.parkBlocker();
172 if (parkBlocker != null) {
173 ps.println(" // parked on " + Objects.toIdentityString(parkBlocker));
174 }
175
176 // blocked on monitor enter or Object.wait
177 if (state == Thread.State.BLOCKED) {
178 Object obj = snapshot.blockedOn();
179 if (obj != null) {
180 ps.println(" // blocked on " + Objects.toIdentityString(obj));
181 }
182 } else if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
183 Object obj = snapshot.waitingOn();
184 if (obj != null) {
185 ps.println(" // waiting on " + Objects.toIdentityString(obj));
186 }
187 }
188
189 StackTraceElement[] stackTrace = snapshot.stackTrace();
190 int depth = 0;
191 while (depth < stackTrace.length) {
192 snapshot.ownedMonitorsAt(depth).forEach(obj -> {
193 ps.print(" // locked ");
194 ps.println(Objects.toIdentityString(obj));
195 });
196 ps.print(" ");
197 ps.println(stackTrace[depth]);
198 depth++;
199 }
200 ps.println();
201 }
202
203 /**
204 * Generate a thread dump in JSON format to the given output stream, UTF-8 encoded.
205 *
206 * This method is invoked by HotSpotDiagnosticMXBean.dumpThreads.
207 */
208 public static void dumpThreadsToJson(OutputStream out) {
209 BufferedOutputStream bos = new BufferedOutputStream(out);
210 PrintStream ps = new PrintStream(bos, false, StandardCharsets.UTF_8);
211 try {
212 dumpThreadsToJson(ps);
213 } finally {
214 ps.flush(); // flushes underlying stream
215 }
216 }
217
218 /**
219 * Generate a thread dump to the given print stream in JSON format.
220 */
221 private static void dumpThreadsToJson(PrintStream out) {
222 try (JsonWriter jsonWriter = JsonWriter.wrap(out)) {
223 jsonWriter.startObject("threadDump");
224
225 jsonWriter.writeProperty("processId", processId());
226 jsonWriter.writeProperty("time", Instant.now());
227 jsonWriter.writeProperty("runtimeVersion", Runtime.version());
228
229 jsonWriter.startArray("threadContainers");
230 allContainers().forEach(c -> dumpThreadsToJson(c, jsonWriter));
231 jsonWriter.endArray();
232
233 jsonWriter.endObject(); // threadDump
234 }
235 }
236
237 /**
238 * Write a thread container to the given JSON writer.
239 */
240 private static void dumpThreadsToJson(ThreadContainer container, JsonWriter jsonWriter) {
241 jsonWriter.startObject();
242 jsonWriter.writeProperty("container", container);
243 jsonWriter.writeProperty("parent", container.parent());
244
245 Thread owner = container.owner();
246 jsonWriter.writeProperty("owner", (owner != null) ? owner.threadId() : null);
247
248 long threadCount = 0;
249 jsonWriter.startArray("threads");
250 Iterator<Thread> threads = container.threads().iterator();
251 while (threads.hasNext()) {
252 Thread thread = threads.next();
253 dumpThreadToJson(thread, jsonWriter);
254 threadCount++;
255 }
256 jsonWriter.endArray(); // threads
257
258 // thread count
259 if (!ThreadContainers.trackAllThreads()) {
260 threadCount = Long.max(threadCount, container.threadCount());
261 }
262 jsonWriter.writeProperty("threadCount", threadCount);
263
264 jsonWriter.endObject();
265 }
266
267 /**
268 * Write a thread to the given JSON writer.
269 */
270 private static void dumpThreadToJson(Thread thread, JsonWriter jsonWriter) {
271 String now = Instant.now().toString();
272 ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
273 Thread.State state = snapshot.threadState();
274 StackTraceElement[] stackTrace = snapshot.stackTrace();
275
276 jsonWriter.startObject();
277 jsonWriter.writeProperty("tid", thread.threadId());
278 jsonWriter.writeProperty("time", now);
279 jsonWriter.writeProperty("name", snapshot.threadName());
280 jsonWriter.writeProperty("state", state);
281
282 // park blocker
283 Object parkBlocker = snapshot.parkBlocker();
284 if (parkBlocker != null) {
285 jsonWriter.writeProperty("parkBlocker", Objects.toIdentityString(parkBlocker));
286 }
287
288 // blocked on monitor enter or Object.wait
289 if (state == Thread.State.BLOCKED) {
290 Object obj = snapshot.blockedOn();
291 if (obj != null) {
292 jsonWriter.writeProperty("blockedOn", Objects.toIdentityString(obj));
293 }
294 } else if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
295 Object obj = snapshot.waitingOn();
296 if (obj != null) {
297 jsonWriter.writeProperty("waitingOn", Objects.toIdentityString(obj));
298 }
299 }
300
301 // stack trace
302 jsonWriter.startArray("stack");
303 Arrays.stream(stackTrace).forEach(jsonWriter::writeProperty);
304 jsonWriter.endArray();
305
306 // monitors owned, skip if none
307 if (snapshot.ownsMonitors()) {
308 jsonWriter.startArray("monitorsOwned");
309 int depth = 0;
310 while (depth < stackTrace.length) {
311 List<Object> objs = snapshot.ownedMonitorsAt(depth).toList();
312 if (!objs.isEmpty()) {
313 jsonWriter.startObject();
314 jsonWriter.writeProperty("depth", depth);
315 jsonWriter.startArray("locks");
316 snapshot.ownedMonitorsAt(depth)
317 .map(Objects::toIdentityString)
318 .forEach(jsonWriter::writeProperty);
319 jsonWriter.endArray();
320 jsonWriter.endObject();
321 }
322 depth++;
323 }
324 jsonWriter.endArray();
325 }
326
327 jsonWriter.endObject();
328 }
329
330 /**
331 * Returns a list of all thread containers that are "reachable" from
332 * the root container.
333 */
334 private static List<ThreadContainer> allContainers() {
335 List<ThreadContainer> containers = new ArrayList<>();
336 collect(ThreadContainers.root(), containers);
337 return containers;
338 }
339
340 private static void collect(ThreadContainer container, List<ThreadContainer> containers) {
341 containers.add(container);
342 container.children().forEach(c -> collect(c, containers));
343 }
344
345 /**
346 * Simple JSON writer to stream objects/arrays to a PrintStream.
347 */
348 private static class JsonWriter implements AutoCloseable {
349 private final PrintStream out;
350
351 // current depth and indentation
352 private int depth = -1;
353 private int indent;
354
355 // indicates if there are properties at depth N
356 private boolean[] hasProperties = new boolean[10];
357
358 private JsonWriter(PrintStream out) {
359 this.out = out;
360 }
361
362 static JsonWriter wrap(PrintStream out) {
363 var writer = new JsonWriter(out);
364 writer.startObject();
365 return writer;
366 }
367
368 /**
369 * Start of object or array.
370 */
371 private void startObject(String name, boolean array) {
372 if (depth >= 0) {
373 if (hasProperties[depth]) {
374 out.println(",");
375 } else {
376 hasProperties[depth] = true; // first property at this depth
377 }
378 }
379 out.print(" ".repeat(indent));
380 if (name != null) {
381 out.print("\"" + name + "\": ");
382 }
383 if (array) {
384 out.println("[");
385 } else {
386 out.println("{");
387 }
388 indent += 2;
389 depth++;
390 hasProperties[depth] = false;
391 }
392
393 /**
394 * End of object or array.
395 */
396 private void endObject(boolean array) {
397 if (hasProperties[depth]) {
398 out.println();
399 hasProperties[depth] = false;
400 }
401 depth--;
402 indent -= 2;
403 out.print(" ".repeat(indent));
404 if (array) {
405 out.print("]");
406 } else {
407 out.print("}");
408 }
409 }
410
411 /**
412 * Write a named property.
413 */
414 void writeProperty(String name, Object obj) {
415 if (hasProperties[depth]) {
416 out.println(",");
417 } else {
418 hasProperties[depth] = true;
419 }
420 out.print(" ".repeat(indent));
421 if (name != null) {
422 out.print("\"" + name + "\": ");
423 }
424 if (obj != null) {
425 out.print("\"" + escape(obj.toString()) + "\"");
426 } else {
427 out.print("null");
428 }
429 }
430
431 /**
432 * Write an unnamed property.
433 */
434 void writeProperty(Object obj) {
435 writeProperty(null, obj);
436 }
437
438 /**
439 * Start named object.
440 */
441 void startObject(String name) {
442 startObject(name, false);
443 }
444
445 /**
446 * Start unnamed object.
447 */
448 void startObject() {
449 startObject(null);
450 }
451
452 /**
453 * End of object.
454 */
455 void endObject() {
456 endObject(false);
457 }
458
459 /**
460 * Start named array.
461 */
462 void startArray(String name) {
463 startObject(name, true);
464 }
465
466 /**
467 * End of array.
468 */
469 void endArray() {
470 endObject(true);
471 }
472
473 @Override
474 public void close() {
475 endObject();
476 out.flush();
477 }
478
479 /**
480 * Escape any characters that need to be escape in the JSON output.
481 */
482 private static String escape(String value) {
483 StringBuilder sb = new StringBuilder();
484 for (int i = 0; i < value.length(); i++) {
485 char c = value.charAt(i);
486 switch (c) {
487 case '"' -> sb.append("\\\"");
488 case '\\' -> sb.append("\\\\");
489 case '/' -> sb.append("\\/");
490 case '\b' -> sb.append("\\b");
491 case '\f' -> sb.append("\\f");
492 case '\n' -> sb.append("\\n");
493 case '\r' -> sb.append("\\r");
494 case '\t' -> sb.append("\\t");
495 default -> {
496 if (c <= 0x1f) {
497 sb.append(String.format("\\u%04x", c));
498 } else {
499 sb.append(c);
500 }
501 }
502 }
503 }
504 return sb.toString();
505 }
506 }
507
508 /**
509 * A ByteArrayOutputStream of bounded size. Once the maximum number of bytes is
510 * written the subsequent bytes are discarded.
511 */
512 private static class BoundedByteArrayOutputStream extends ByteArrayOutputStream {
513 final int max;
514 BoundedByteArrayOutputStream(int max) {
515 this.max = max;
516 }
517 @Override
518 public void write(int b) {
519 if (max < count) {
520 super.write(b);
521 }
522 }
523 @Override
524 public void write(byte[] b, int off, int len) {
525 int remaining = max - count;
|