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 ps) {
222 try (JsonWriter jsonWriter = JsonWriter.wrap(ps)) {
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 Instant now = Instant.now();
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.startObject("parkBlocker");
286 jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker));
287 // TBD add exclusiveOwnerThread if AbstractOwnableSynchronizer
288 jsonWriter.endObject();
289 }
290
291 // blocked on monitor enter or Object.wait
292 if (state == Thread.State.BLOCKED) {
293 Object obj = snapshot.blockedOn();
294 if (obj != null) {
295 jsonWriter.writeProperty("blockedOn", Objects.toIdentityString(obj));
296 }
297 } else if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
298 Object obj = snapshot.waitingOn();
299 if (obj != null) {
300 jsonWriter.writeProperty("waitingOn", Objects.toIdentityString(obj));
301 }
302 }
303
304 // stack trace
305 jsonWriter.startArray("stack");
306 Arrays.stream(stackTrace).forEach(jsonWriter::writeProperty);
307 jsonWriter.endArray();
308
309 // monitors owned, skip if none
310 if (snapshot.ownsMonitors()) {
311 jsonWriter.startArray("monitorsOwned");
312 int depth = 0;
313 while (depth < stackTrace.length) {
314 List<Object> objs = snapshot.ownedMonitorsAt(depth).toList();
315 if (!objs.isEmpty()) {
316 jsonWriter.startObject();
317 jsonWriter.writeProperty("depth", depth);
318 jsonWriter.startArray("locks");
319 snapshot.ownedMonitorsAt(depth)
320 .map(Objects::toIdentityString)
321 .forEach(jsonWriter::writeProperty);
322 jsonWriter.endArray();
323 jsonWriter.endObject();
324 }
325 depth++;
326 }
327 jsonWriter.endArray();
328 }
329
330 jsonWriter.endObject();
331 }
332
333 /**
334 * Returns a list of all thread containers that are "reachable" from
335 * the root container.
336 */
337 private static List<ThreadContainer> allContainers() {
338 List<ThreadContainer> containers = new ArrayList<>();
339 collect(ThreadContainers.root(), containers);
340 return containers;
341 }
342
343 private static void collect(ThreadContainer container, List<ThreadContainer> containers) {
344 containers.add(container);
345 container.children().forEach(c -> collect(c, containers));
346 }
347
348 /**
349 * Simple JSON writer to stream objects/arrays to a PrintStream.
350 */
351 private static class JsonWriter implements AutoCloseable {
352 private final PrintStream out;
353
354 // current depth and indentation
355 private int depth = -1;
356 private int indent;
357
358 // indicates if there are properties at depth N
359 private boolean[] hasProperties = new boolean[10];
360
361 private JsonWriter(PrintStream out) {
362 this.out = out;
363 }
364
365 static JsonWriter wrap(PrintStream out) {
366 var writer = new JsonWriter(out);
367 writer.startObject();
368 return writer;
369 }
370
371 /**
372 * Start of object or array.
373 */
374 private void startObject(String name, boolean array) {
375 if (depth >= 0) {
376 if (hasProperties[depth]) {
377 out.println(",");
378 } else {
379 hasProperties[depth] = true; // first property at this depth
380 }
381 }
382 out.print(" ".repeat(indent));
383 if (name != null) {
384 out.print("\"" + name + "\": ");
385 }
386 if (array) {
387 out.println("[");
388 } else {
389 out.println("{");
390 }
391 indent += 2;
392 depth++;
393 hasProperties[depth] = false;
394 }
395
396 /**
397 * End of object or array.
398 */
399 private void endObject(boolean array) {
400 if (hasProperties[depth]) {
401 out.println();
402 hasProperties[depth] = false;
403 }
404 depth--;
405 indent -= 2;
406 out.print(" ".repeat(indent));
407 if (array) {
408 out.print("]");
409 } else {
410 out.print("}");
411 }
412 }
413
414 /**
415 * Write a property.
416 * @param name the property name, null for an unnamed property
417 * @param obj the value or null
418 */
419 void writeProperty(String name, Object obj) {
420 if (hasProperties[depth]) {
421 out.println(",");
422 } else {
423 hasProperties[depth] = true;
424 }
425 out.print(" ".repeat(indent));
426 if (name != null) {
427 out.print("\"" + name + "\": ");
428 }
429 switch (obj) {
430 case Number _ -> out.print(obj);
431 case null -> out.print("null");
432 default -> out.print("\"" + escape(obj.toString()) + "\"");
433 }
434 }
435
436 /**
437 * Write an unnamed property.
438 */
439 void writeProperty(Object obj) {
440 writeProperty(null, obj);
441 }
442
443 /**
444 * Start named object.
445 */
446 void startObject(String name) {
447 startObject(name, false);
448 }
449
450 /**
451 * Start unnamed object.
452 */
453 void startObject() {
454 startObject(null);
455 }
456
457 /**
458 * End of object.
459 */
460 void endObject() {
461 endObject(false);
462 }
463
464 /**
465 * Start named array.
466 */
467 void startArray(String name) {
468 startObject(name, true);
469 }
470
471 /**
472 * End of array.
473 */
474 void endArray() {
475 endObject(true);
476 }
477
478 @Override
479 public void close() {
480 endObject();
481 out.flush();
482 }
483
484 /**
485 * Escape any characters that need to be escape in the JSON output.
486 */
487 private static String escape(String value) {
488 StringBuilder sb = new StringBuilder();
489 for (int i = 0; i < value.length(); i++) {
490 char c = value.charAt(i);
491 switch (c) {
492 case '"' -> sb.append("\\\"");
493 case '\\' -> sb.append("\\\\");
494 case '/' -> sb.append("\\/");
495 case '\b' -> sb.append("\\b");
496 case '\f' -> sb.append("\\f");
497 case '\n' -> sb.append("\\n");
498 case '\r' -> sb.append("\\r");
499 case '\t' -> sb.append("\\t");
500 default -> {
501 if (c <= 0x1f) {
502 sb.append(String.format("\\u%04x", c));
503 } else {
504 sb.append(c);
505 }
506 }
507 }
508 }
509 return sb.toString();
510 }
511 }
512
513 /**
514 * A ByteArrayOutputStream of bounded size. Once the maximum number of bytes is
515 * written the subsequent bytes are discarded.
516 */
517 private static class BoundedByteArrayOutputStream extends ByteArrayOutputStream {
518 final int max;
519 BoundedByteArrayOutputStream(int max) {
520 this.max = max;
521 }
522 @Override
523 public void write(int b) {
524 if (max < count) {
525 super.write(b);
526 }
527 }
528 @Override
529 public void write(byte[] b, int off, int len) {
530 int remaining = max - count;
|