1 /*
  2  * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test
 26  * @bug 8356870
 27  * @summary Test HotSpotDiagnosticMXBean.dumpThreads with a thread owning a monitor for
 28  *     an object that is scalar replaced
 29  * @requires vm.compMode != "Xcomp"
 30  * @requires (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4)
 31  * @modules jdk.management
 32  * @library /test/lib
 33  * @run main/othervm -XX:CompileCommand=inline,java/lang/String*.* DumpThreadsWithEliminatedLock plain platform
 34  * @run main/othervm -XX:CompileCommand=inline,java/lang/String*.* DumpThreadsWithEliminatedLock plain virtual
 35  * @run main/othervm -XX:CompileCommand=inline,java/lang/String*.* DumpThreadsWithEliminatedLock json platform
 36  * @run main/othervm -XX:CompileCommand=inline,java/lang/String*.* DumpThreadsWithEliminatedLock json virtual
 37  */
 38 
 39 import java.io.IOException;
 40 import java.io.UncheckedIOException;
 41 import java.lang.management.ManagementFactory;
 42 import java.nio.file.Files;
 43 import java.nio.file.Path;
 44 import java.time.Instant;
 45 import java.util.List;
 46 import java.util.concurrent.CountDownLatch;
 47 import java.util.concurrent.ThreadFactory;
 48 import java.util.concurrent.atomic.AtomicBoolean;
 49 import java.util.concurrent.atomic.AtomicReference;
 50 import java.util.stream.Stream;
 51 import com.sun.management.HotSpotDiagnosticMXBean;
 52 import jdk.test.lib.threaddump.ThreadDump;
 53 import jdk.test.lib.thread.VThreadRunner;
 54 
 55 public class DumpThreadsWithEliminatedLock {
 56 
 57     public static void main(String[] args) throws Exception {
 58         boolean plain = switch (args[0]) {
 59             case "plain" -> true;
 60             case "json"  -> false;
 61             default      -> throw new RuntimeException("Unknown dump format");
 62         };
 63 
 64         ThreadFactory factory = switch (args[1]) {
 65             case "platform" -> Thread.ofPlatform().factory();
 66             case "virtual"  -> Thread.ofVirtual().factory();
 67             default         -> throw new RuntimeException("Unknown thread kind");
 68         };
 69 
 70         // need at least two carriers for JTREG_TEST_THREAD_FACTORY=Virtual
 71         if (Thread.currentThread().isVirtual()) {
 72             VThreadRunner.ensureParallelism(2);
 73         }
 74 
 75         // A thread that spins creating and adding to a StringBuffer. StringBuffer is
 76         // synchronized, assume object will be scalar replaced and the lock eliminated.
 77         var started = new CountDownLatch(1);
 78         var done = new AtomicBoolean();
 79         var ref = new AtomicReference<String>();
 80         Thread thread = factory.newThread(() -> {
 81             started.countDown();
 82             while (!done.get()) {
 83                 StringBuffer sb = new StringBuffer();
 84                 sb.append(System.currentTimeMillis());
 85                 String s = sb.toString();
 86                 ref.set(s);
 87             }
 88         });
 89         try {
 90             thread.start();
 91             started.await();
 92             if (plain) {
 93                 testPlainFormat();
 94             } else {
 95                 testJsonFormat(thread.threadId());
 96             }
 97         } finally {
 98             done.set(true);
 99             thread.join();
100         }
101     }
102 
103     /**
104      * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in plain text
105      * format until "lock is eliminated" is found in the output.
106      */
107     private static void testPlainFormat() {
108         try {
109             Path file = genOutputPath(".txt");
110             boolean found = false;
111             int attempts = 0;
112             while (!found) {
113                 attempts++;
114                 Files.deleteIfExists(file);
115                 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
116                         .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.TEXT_PLAIN);
117                 try (Stream<String> stream = Files.lines(file)) {
118                     found = stream.map(String::trim)
119                             .anyMatch(l -> l.contains("- lock is eliminated"));
120                 }
121                 System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found);
122             }
123         } catch (IOException ioe) {
124             throw new UncheckedIOException(ioe);
125         }
126     }
127 
128     /**
129      * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in JSON format
130      * until the monitorsOwned.locks array for the given thread has a null lock.
131      */
132     private static void testJsonFormat(long tid) {
133         try {
134             Path file = genOutputPath(".json");
135             boolean found = false;
136             int attempts = 0;
137             while (!found) {
138                 attempts++;
139                 Files.deleteIfExists(file);
140                 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
141                         .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON);
142 
143                 // parse thread dump as JSON and find thread
144                 String jsonText = Files.readString(file);
145                 ThreadDump threadDump = ThreadDump.parse(jsonText);
146                 ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
147                         .findThread(tid)
148                         .orElse(null);
149                 if (ti == null) {
150                     throw new RuntimeException("Thread " + tid + " not found in thread dump");
151                 }
152 
153                 // look for null element in ownedMonitors/locks array
154                 found = ti.ownedMonitors()
155                         .values()
156                         .stream()
157                         .flatMap(List::stream)
158                         .anyMatch(o -> o == null);
159                 System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found);
160             }
161         } catch (IOException ioe) {
162             throw new UncheckedIOException(ioe);
163         }
164     }
165 
166     /**
167      * Generate a file path with the given suffix to use as an output file.
168      */
169     private static Path genOutputPath(String suffix) throws IOException {
170         Path dir = Path.of(".").toAbsolutePath();
171         Path file = Files.createTempFile(dir, "dump", suffix);
172         Files.delete(file);
173         return file;
174     }
175 }