< prev index next >

src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java

Print this page

 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;
< prev index next >