< prev index next >

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

Print this page

 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      * JSON is schema-less and the thread dump format will evolve over time.
255      * {@code HotSpotDiagnosticMXBean.dumpThreads} links to a JSON file that documents
256      * the latest/current format. A system property can be used to generate the thread
257      * dump in older formats if necessary.
258      */
259     private static class JsonFormat {
260         private static final String JSON_FORMAT_VERSION_PROP =
261                 "com.sun.management.HotSpotDiagnosticMXBean.dumpThreads.format";
262         static final int JSON_FORMAT_V1 = 1;
263         static final int JSON_FORMAT_V2 = 2;
264         private static final int JSON_FORMAT_LATEST = JSON_FORMAT_V2;
265         private static final int JSON_FORMAT;
266         static {
267             int ver = Integer.getInteger(JSON_FORMAT_VERSION_PROP, JSON_FORMAT_LATEST);
268             JSON_FORMAT = Math.clamp(ver, JSON_FORMAT_V1, JSON_FORMAT_LATEST);
269         }
270 
271         static int formatVersion() {
272             return JSON_FORMAT;
273         }
274     }
275 
276     /**
277      * Generate a thread dump to the given text stream in JSON format.
278      * @throws UncheckedIOException if an I/O error occurs
279      */
280     private static void dumpThreadsToJson(TextWriter textWriter) {
281         int format = JsonFormat.formatVersion();
282         var jsonWriter = new JsonWriter(textWriter, (format == JsonFormat.JSON_FORMAT_V1));

283         jsonWriter.startObject();  // top-level object
284         jsonWriter.startObject("threadDump");
285         if (format > JsonFormat.JSON_FORMAT_V1) {
286             jsonWriter.writeProperty("formatVersion", format);
287         }
288 
289         jsonWriter.writeLongProperty("processId", processId());
290         jsonWriter.writeProperty("time", Instant.now());
291         jsonWriter.writeProperty("runtimeVersion", Runtime.version());
292 
293         jsonWriter.startArray("threadContainers");
294         dumpThreads(ThreadContainers.root(), jsonWriter);
295         jsonWriter.endArray();
296 
297         jsonWriter.endObject();  // threadDump
298 
299         jsonWriter.endObject();  // end of top-level object
300     }
301 
302     /**

330         if (!ThreadContainers.trackAllThreads()) {
331             threadCount = Long.max(threadCount, container.threadCount());
332         }
333         jsonWriter.writeLongProperty("threadCount", threadCount);
334 
335         jsonWriter.endObject();
336 
337         // the children of the thread container follow
338         container.children().forEach(c -> dumpThreads(c, jsonWriter));
339     }
340 
341     /**
342      * Write a thread to the given JSON writer.
343      * @return true if the thread dump was written, false otherwise
344      * @throws UncheckedIOException if an I/O error occurs
345      */
346     private static boolean dumpThread(Thread thread, JsonWriter jsonWriter) {
347         Instant now = Instant.now();
348         ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
349         if (snapshot == null) {
350             return false; // thread terminated
351         }
352         Thread.State state = snapshot.threadState();
353         StackTraceElement[] stackTrace = snapshot.stackTrace();
354 
355         jsonWriter.startObject();
356         jsonWriter.writeLongProperty("tid", thread.threadId());
357         jsonWriter.writeProperty("time", now);
358         if (thread.isVirtual()) {
359             jsonWriter.writeProperty("virtual", Boolean.TRUE);
360         }
361         jsonWriter.writeProperty("name", snapshot.threadName());
362         jsonWriter.writeProperty("state", state);
363 
364         // park blocker
365         Object parkBlocker = snapshot.parkBlocker();
366         if (parkBlocker != null) {
367             // parkBlocker is an object to allow for exclusiveOwnerThread in the future
368             jsonWriter.startObject("parkBlocker");
369             jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker));
370             if (snapshot.parkBlockerOwner() instanceof Thread owner) {

423     private static class JsonWriter {
424         private static class Node {
425             final boolean isArray;
426             int propertyCount;
427             Node(boolean isArray) {
428                 this.isArray = isArray;
429             }
430             boolean isArray() {
431                 return isArray;
432             }
433             int propertyCount() {
434                 return propertyCount;
435             }
436             int getAndIncrementPropertyCount() {
437                 int old = propertyCount;
438                 propertyCount++;
439                 return old;
440             }
441         }
442         private final Deque<Node> stack = new ArrayDeque<>();
443         private final boolean generateLongsAsString;
444         private final TextWriter writer;


445 
446         JsonWriter(TextWriter writer, boolean generateLongsAsString) {
447             this.writer = writer;

448             this.generateLongsAsString = generateLongsAsString;
449         }
450 


















451         private void indent() {
452             int indent = stack.size() * 2;
453             writer.print(" ".repeat(indent));


454         }
455 
456         /**
457          * Start of object or array.
458          */
459         private void startObject(String name, boolean isArray) {
460             if (!stack.isEmpty()) {
461                 Node node = stack.peek();
462                 if (node.getAndIncrementPropertyCount() > 0) {
463                     writer.println(",");
464                 }
465             }
466             indent();
467             if (name != null) {
468                 writer.print("\"" + name + "\": ");

469             }
470             writer.println(isArray ? "[" : "{");
471             stack.push(new Node(isArray));
472         }
473 
474         /**
475          * End of object or array.
476          */
477         private void endObject(boolean isArray) {
478             Node node = stack.pop();
479             if (node.isArray() != isArray)
480                 throw new IllegalStateException();
481             if (node.propertyCount() > 0) {
482                 writer.println();
483             }
484             indent();
485             writer.print(isArray ? "]" : "}");
486         }
487 
488         /**
489          * Write a property.
490          * @param name the property name, null for an unnamed property
491          * @param obj the value or null
492          */
493         void writeProperty(String name, Object obj) {
494             Node node = stack.peek();
495             assert node != null;
496             if (node.getAndIncrementPropertyCount() > 0) {
497                 writer.println(",");
498             }
499             indent();
500             if (name != null) {
501                 writer.print("\"" + name + "\": ");
502             }
503             switch (obj) {
504                 case Number _  -> writer.print(obj);
505                 case Boolean _ -> writer.print(obj);
506                 case null      -> writer.print("null");
507                 default        -> writer.print("\"" + escape(obj.toString()) + "\"");
508             }
509         }
510 
511         /**
512          * Write a property with a long value. If the value is outside the "interop"
513          * range of IEEE-754 double-precision floating point (64-bit) then it is
514          * written as a string.
515          */
516         void writeLongProperty(String name, long value) {
517             if (generateLongsAsString || value < -0x1FFFFFFFFFFFFFL || value > 0x1FFFFFFFFFFFFFL) {
518                 writeProperty(name, Long.toString(value));
519             } else {
520                 writeProperty(name, value);
521             }
522         }
523 
524         /**
525          * Write an unnamed property.
526          */
527         void writeProperty(Object obj) {

 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 prettyPrint) {
 87         if (file == null || file.equals("-")) {
 88             return dumpThreadsToByteArray(true, prettyPrint, MAX_BYTE_ARRAY_SIZE);
 89         } else {
 90             return dumpThreadsToFile(file, okayToOverwrite, true, prettyPrint);
 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 prettyPrint, int maxSize) {
101         var out = new BoundedByteArrayOutputStream(maxSize);
102         try (out; var writer = new TextWriter(out)) {
103             if (json) {
104                 dumpThreadsToJson(writer, prettyPrint);
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 prettyPrint) {
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, prettyPrint);
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      * JSON is schema-less and the thread dump format will evolve over time.
259      * {@code HotSpotDiagnosticMXBean.dumpThreads} links to a JSON file that documents
260      * the latest/current format. A system property can be used to generate the thread
261      * dump in older formats if necessary.
262      */
263     private static class JsonFormat {
264         private static final String JSON_FORMAT_VERSION_PROP =
265                 "com.sun.management.HotSpotDiagnosticMXBean.dumpThreads.format";
266         static final int JSON_FORMAT_V1 = 1;
267         static final int JSON_FORMAT_V2 = 2;
268         private static final int JSON_FORMAT_LATEST = JSON_FORMAT_V2;
269         private static final int JSON_FORMAT;
270         static {
271             int ver = Integer.getInteger(JSON_FORMAT_VERSION_PROP, JSON_FORMAT_LATEST);
272             JSON_FORMAT = Math.clamp(ver, JSON_FORMAT_V1, JSON_FORMAT_LATEST);
273         }
274 
275         static int formatVersion() {
276             return JSON_FORMAT;
277         }
278     }
279 
280     /**
281      * Generate a thread dump to the given text stream in JSON format.
282      * @throws UncheckedIOException if an I/O error occurs
283      */
284     private static void dumpThreadsToJson(TextWriter textWriter, boolean prettyPrint) {
285         int format = JsonFormat.formatVersion();
286         boolean generateLongsAsString = (format == JsonFormat.JSON_FORMAT_V1);
287         var jsonWriter = new JsonWriter(textWriter, prettyPrint, generateLongsAsString);
288         jsonWriter.startObject();  // top-level object
289         jsonWriter.startObject("threadDump");
290         if (format > JsonFormat.JSON_FORMAT_V1) {
291             jsonWriter.writeProperty("formatVersion", format);
292         }
293 
294         jsonWriter.writeLongProperty("processId", processId());
295         jsonWriter.writeProperty("time", Instant.now());
296         jsonWriter.writeProperty("runtimeVersion", Runtime.version());
297 
298         jsonWriter.startArray("threadContainers");
299         dumpThreads(ThreadContainers.root(), jsonWriter);
300         jsonWriter.endArray();
301 
302         jsonWriter.endObject();  // threadDump
303 
304         jsonWriter.endObject();  // end of top-level object
305     }
306 
307     /**

335         if (!ThreadContainers.trackAllThreads()) {
336             threadCount = Long.max(threadCount, container.threadCount());
337         }
338         jsonWriter.writeLongProperty("threadCount", threadCount);
339 
340         jsonWriter.endObject();
341 
342         // the children of the thread container follow
343         container.children().forEach(c -> dumpThreads(c, jsonWriter));
344     }
345 
346     /**
347      * Write a thread to the given JSON writer.
348      * @return true if the thread dump was written, false otherwise
349      * @throws UncheckedIOException if an I/O error occurs
350      */
351     private static boolean dumpThread(Thread thread, JsonWriter jsonWriter) {
352         Instant now = Instant.now();
353         ThreadSnapshot snapshot = ThreadSnapshot.of(thread);
354         if (snapshot == null) {
355             return false; // thread not alive
356         }
357         Thread.State state = snapshot.threadState();
358         StackTraceElement[] stackTrace = snapshot.stackTrace();
359 
360         jsonWriter.startObject();
361         jsonWriter.writeLongProperty("tid", thread.threadId());
362         jsonWriter.writeProperty("time", now);
363         if (thread.isVirtual()) {
364             jsonWriter.writeProperty("virtual", Boolean.TRUE);
365         }
366         jsonWriter.writeProperty("name", snapshot.threadName());
367         jsonWriter.writeProperty("state", state);
368 
369         // park blocker
370         Object parkBlocker = snapshot.parkBlocker();
371         if (parkBlocker != null) {
372             // parkBlocker is an object to allow for exclusiveOwnerThread in the future
373             jsonWriter.startObject("parkBlocker");
374             jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker));
375             if (snapshot.parkBlockerOwner() instanceof Thread owner) {

428     private static class JsonWriter {
429         private static class Node {
430             final boolean isArray;
431             int propertyCount;
432             Node(boolean isArray) {
433                 this.isArray = isArray;
434             }
435             boolean isArray() {
436                 return isArray;
437             }
438             int propertyCount() {
439                 return propertyCount;
440             }
441             int getAndIncrementPropertyCount() {
442                 int old = propertyCount;
443                 propertyCount++;
444                 return old;
445             }
446         }
447         private final Deque<Node> stack = new ArrayDeque<>();

448         private final TextWriter writer;
449         private final boolean prettyPrint;  // pretty print or minify
450         private final boolean generateLongsAsString;
451 
452         JsonWriter(TextWriter writer, boolean prettyPrint, boolean generateLongsAsString) {
453             this.writer = writer;
454             this.prettyPrint = prettyPrint;
455             this.generateLongsAsString = generateLongsAsString;
456         }
457 
458         private void print(Object obj) {
459             writer.print(obj);
460         }
461 
462         private void println(Object obj) {
463             if (prettyPrint) {
464                 writer.println(obj);
465             } else {
466                 writer.print(obj);
467             }
468         }
469 
470         private void println() {
471             if (prettyPrint) {
472                 writer.println();
473             }
474         }
475 
476         private void indent() {
477             if (prettyPrint) {
478                 int indent = stack.size() * 2;
479                 writer.print(" ".repeat(indent));
480             }
481         }
482 
483         /**
484          * Start of object or array.
485          */
486         private void startObject(String name, boolean isArray) {
487             if (!stack.isEmpty()) {
488                 Node node = stack.peek();
489                 if (node.getAndIncrementPropertyCount() > 0) {
490                     println(",");
491                 }
492             }
493             indent();
494             if (name != null) {
495                 String gap = prettyPrint ? " " : "";
496                 print("\"" + name + "\":" + gap);
497             }
498             println(isArray ? "[" : "{");
499             stack.push(new Node(isArray));
500         }
501 
502         /**
503          * End of object or array.
504          */
505         private void endObject(boolean isArray) {
506             Node node = stack.pop();
507             if (node.isArray() != isArray)
508                 throw new IllegalStateException();
509             if (node.propertyCount() > 0) {
510                 println();
511             }
512             indent();
513             print(isArray ? "]" : "}");
514         }
515 
516         /**
517          * Write a property.
518          * @param name the property name, null for an unnamed property
519          * @param obj the value or null
520          */
521         void writeProperty(String name, Object obj) {
522             Node node = stack.peek();
523             assert node != null;
524             if (node.getAndIncrementPropertyCount() > 0) {
525                 println(",");
526             }
527             indent();
528             if (name != null) {
529                 print("\"" + name + "\": ");
530             }
531             switch (obj) {
532                 case Number _  -> print(obj);
533                 case Boolean _ -> print(obj);
534                 case null      -> print("null");
535                 default        -> print("\"" + escape(obj.toString()) + "\"");
536             }
537         }
538 
539         /**
540          * Write a property with a long value. If the value is outside the "interop"
541          * range of IEEE-754 double-precision floating point (64-bit) then it is
542          * written as a string.
543          */
544         void writeLongProperty(String name, long value) {
545             if (generateLongsAsString || value < -0x1FFFFFFFFFFFFFL || value > 0x1FFFFFFFFFFFFFL) {
546                 writeProperty(name, Long.toString(value));
547             } else {
548                 writeProperty(name, value);
549             }
550         }
551 
552         /**
553          * Write an unnamed property.
554          */
555         void writeProperty(Object obj) {
< prev index next >