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 }