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.debug & (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 DumpThreadsWithEliminatedLock plain platform 34 * @run main/othervm DumpThreadsWithEliminatedLock plain virtual 35 * @run main/othervm DumpThreadsWithEliminatedLock json platform 36 * @run main/othervm 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.ThreadFactory; 47 import java.util.concurrent.atomic.AtomicBoolean; 48 import java.util.concurrent.atomic.AtomicReference; 49 import java.util.stream.Stream; 50 import com.sun.management.HotSpotDiagnosticMXBean; 51 import jdk.test.lib.threaddump.ThreadDump; 52 import jdk.test.lib.thread.VThreadRunner; 53 54 public class DumpThreadsWithEliminatedLock { 55 56 public static void main(String[] args) throws Exception { 57 boolean plain = switch (args[0]) { 58 case "plain" -> true; 59 case "json" -> false; 60 default -> throw new RuntimeException("Unknown dump format"); 61 }; 62 63 ThreadFactory factory = switch (args[1]) { 64 case "platform" -> Thread.ofPlatform().factory(); 65 case "virtual" -> Thread.ofVirtual().factory(); 66 default -> throw new RuntimeException("Unknown thread kind"); 67 }; 68 69 // need at least two carriers for JTREG_TEST_THREAD_FACTORY=Virtual 70 if (Thread.currentThread().isVirtual()) { 71 VThreadRunner.ensureParallelism(2); 72 } 73 74 // A thread that spins creating and adding to a StringBuffer. StringBuffer is 75 // synchronized, assume object will be scalar replaced and the lock eliminated. 76 var done = new AtomicBoolean(); 77 var ref = new AtomicReference<String>(); 78 Thread thread = factory.newThread(() -> { 79 while (!done.get()) { 80 StringBuffer sb = new StringBuffer(); 81 sb.append(System.currentTimeMillis()); 82 String s = sb.toString(); 83 ref.set(s); 84 } 85 }); 86 try { 87 thread.start(); 88 if (plain) { 89 testPlainFormat(); 90 } else { 91 testJsonFormat(thread.threadId()); 92 } 93 } finally { 94 done.set(true); 95 thread.join(); 96 } 97 } 98 99 /** 100 * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in plain text 101 * format until "lock is eliminated" is found in the output. 102 */ 103 private static void testPlainFormat() { 104 try { 105 Path file = genOutputPath(".txt"); 106 boolean found = false; 107 int attempts = 0; 108 while (!found) { 109 attempts++; 110 Files.deleteIfExists(file); 111 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) 112 .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.TEXT_PLAIN); 113 try (Stream<String> stream = Files.lines(file)) { 114 found = stream.map(String::trim) 115 .anyMatch(l -> l.contains("- lock is eliminated")); 116 } 117 System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found); 118 } 119 } catch (IOException ioe) { 120 throw new UncheckedIOException(ioe); 121 } 122 } 123 124 /** 125 * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in JSON format 126 * until the monitorsOwned.locks array for the given thread has a null lock. 127 */ 128 private static void testJsonFormat(long tid) { 129 try { 130 Path file = genOutputPath(".json"); 131 boolean found = false; 132 int attempts = 0; 133 while (!found) { 134 attempts++; 135 Files.deleteIfExists(file); 136 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) 137 .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON); 138 139 // parse thread dump as JSON and find thread 140 String jsonText = Files.readString(file); 141 ThreadDump threadDump = ThreadDump.parse(jsonText); 142 ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer() 143 .findThread(tid) 144 .orElse(null); 145 if (ti == null) { 146 throw new RuntimeException("Thread " + tid + " not found in thread dump"); 147 } 148 149 // look for null element in ownedMonitors/locks array 150 found = ti.ownedMonitors() 151 .values() 152 .stream() 153 .flatMap(List::stream) 154 .anyMatch(o -> o == null); 155 System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found); 156 } 157 } catch (IOException ioe) { 158 throw new UncheckedIOException(ioe); 159 } 160 } 161 162 /** 163 * Generate a file path with the given suffix to use as an output file. 164 */ 165 private static Path genOutputPath(String suffix) throws IOException { 166 Path dir = Path.of(".").toAbsolutePath(); 167 Path file = Files.createTempFile(dir, "dump", suffix); 168 Files.delete(file); 169 return file; 170 } 171 }