1 /* 2 * Copyright (c) 2017, 2018, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 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 26 package jdk.jfr.event.runtime; 27 28 import jdk.jfr.Recording; 29 import jdk.jfr.consumer.*; 30 import jdk.test.lib.Asserts; 31 import jdk.test.lib.dcmd.PidJcmdExecutor; 32 import jdk.test.lib.jfr.EventNames; 33 import jdk.test.lib.jfr.Events; 34 import jdk.test.lib.process.OutputAnalyzer; 35 36 import java.util.*; 37 import java.util.concurrent.FutureTask; 38 import java.util.stream.Collectors; 39 40 /** 41 * @test 42 * @key jfr 43 * 44 * @library /lib / 45 * 46 * @run main/othervm jdk.jfr.event.runtime.TestBiasedLockRevocationEvents 47 */ 48 public class TestBiasedLockRevocationEvents { 49 50 public static void main(String[] args) throws Throwable { 51 testSingleRevocation(); 52 testBulkRevocation(); 53 testSelfRevocation(); 54 testExitedThreadRevocation(); 55 testBulkRevocationNoRebias(); 56 testRevocationSafepointIdCorrelation(); 57 } 58 59 // Default value of BiasedLockingBulkRebiasThreshold is 20, and BiasedLockingBulkRevokeTreshold is 40. 60 // Using a value that will hit the first threshold once, and the second one the next time. 61 private static final int BULK_REVOKE_THRESHOLD = 25; 62 63 static void touch(Object lock) { 64 synchronized(lock) { 65 } 66 } 67 68 static Thread triggerRevocation(int numRevokes, Class<?> lockClass) throws Throwable { 69 Object[] locks = new Object[numRevokes]; 70 for (int i = 0; i < locks.length; ++i) { 71 locks[i] = lockClass.getDeclaredConstructor().newInstance(); 72 touch(locks[i]); 73 } 74 75 Thread biasBreaker = new Thread("BiasBreaker") { 76 @Override 77 public void run() { 78 for (Object lock : locks) { 79 touch(lock); 80 } 81 } 82 }; 83 84 biasBreaker.start(); 85 biasBreaker.join(); 86 87 return biasBreaker; 88 } 89 90 // Basic stack trace validation, checking the name of the leaf method 91 static void validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName) { 92 List<RecordedFrame> frames = stackTrace.getFrames(); 93 Asserts.assertFalse(frames.isEmpty()); 94 String name = frames.get(0).getMethod().getName(); 95 Asserts.assertEquals(name, leafMethodName); 96 } 97 98 // Validates that the given stack trace refers to lock.touch(); in triggerRevocation 99 static void validateStackTrace(RecordedStackTrace stackTrace) { 100 validateStackTrace(stackTrace, "touch"); 101 } 102 103 // Retrieve all biased lock revocation events related to the provided lock class, sorted by start time 104 static List<RecordedEvent> getRevocationEvents(Recording recording, String fieldName, Class<?> lockClass) throws Throwable { 105 return Events.fromRecording(recording).stream() 106 .filter(e -> ((RecordedClass)e.getValue(fieldName)).getName().equals(lockClass.getName())) 107 .sorted(Comparator.comparing(RecordedEvent::getStartTime)) 108 .collect(Collectors.toList()); 109 } 110 111 static void testSingleRevocation() throws Throwable { 112 class MyLock {}; 113 114 Recording recording = new Recording(); 115 116 recording.enable(EventNames.BiasedLockRevocation); 117 recording.start(); 118 119 Thread biasBreaker = triggerRevocation(1, MyLock.class); 120 121 recording.stop(); 122 List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class); 123 Asserts.assertEQ(events.size(), 1); 124 125 RecordedEvent event = events.get(0); 126 Events.assertEventThread(event, biasBreaker); 127 Events.assertEventThread(event, "previousOwner", Thread.currentThread()); 128 129 RecordedClass lockClass = event.getValue("lockClass"); 130 Asserts.assertEquals(lockClass.getName(), MyLock.class.getName()); 131 132 validateStackTrace(event.getStackTrace()); 133 } 134 135 static void testBulkRevocation() throws Throwable { 136 class MyLock {}; 137 138 Recording recording = new Recording(); 139 140 recording.enable(EventNames.BiasedLockClassRevocation); 141 recording.start(); 142 143 Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 144 145 recording.stop(); 146 List<RecordedEvent> events = getRevocationEvents(recording, "revokedClass", MyLock.class); 147 Asserts.assertEQ(events.size(), 1); 148 149 RecordedEvent event = events.get(0); 150 Events.assertEventThread(event, biasBreaker); 151 Events.assertField(event, "disableBiasing").equal(false); 152 153 RecordedClass lockClass = event.getValue("revokedClass"); 154 Asserts.assertEquals(lockClass.getName(), MyLock.class.getName()); 155 156 validateStackTrace(event.getStackTrace()); 157 } 158 159 static void testSelfRevocation() throws Throwable { 160 class MyLock {}; 161 162 Recording recording = new Recording(); 163 164 recording.enable(EventNames.BiasedLockSelfRevocation); 165 recording.start(); 166 167 MyLock l = new MyLock(); 168 touch(l); 169 Thread.holdsLock(l); 170 171 recording.stop(); 172 List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class); 173 Asserts.assertEQ(events.size(), 1); 174 175 RecordedEvent event = events.get(0); 176 Events.assertEventThread(event, Thread.currentThread()); 177 178 validateStackTrace(event.getStackTrace(), "holdsLock"); 179 } 180 181 static void testExitedThreadRevocation() throws Throwable { 182 class MyLock {}; 183 184 Recording recording = new Recording(); 185 186 recording.enable(EventNames.BiasedLockRevocation); 187 recording.start(); 188 189 FutureTask<MyLock> lockerTask = new FutureTask<>(() -> { 190 MyLock l = new MyLock(); 191 touch(l); 192 return l; 193 }); 194 195 Thread locker = new Thread(lockerTask, "BiasLocker"); 196 locker.start(); 197 locker.join(); 198 199 // Even after joining, the VM has a bit more work to do before the thread is actually removed 200 // from the threads list. Ensure that this has happened before proceeding. 201 while (true) { 202 PidJcmdExecutor jcmd = new PidJcmdExecutor(); 203 OutputAnalyzer oa = jcmd.execute("Thread.print", true); 204 String lockerThreadFound = oa.firstMatch("BiasLocker"); 205 if (lockerThreadFound == null) { 206 break; 207 } 208 }; 209 210 MyLock l = lockerTask.get(); 211 touch(l); 212 213 recording.stop(); 214 List<RecordedEvent> events = getRevocationEvents(recording, "lockClass", MyLock.class); 215 Asserts.assertEQ(events.size(), 1); 216 217 RecordedEvent event = events.get(0); 218 Events.assertEventThread(event, Thread.currentThread()); 219 // Previous owner will usually be null, but can also be a thread that 220 // was created after the BiasLocker thread exited due to address reuse. 221 RecordedThread prevOwner = event.getValue("previousOwner"); 222 if (prevOwner != null) { 223 Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker"); 224 } 225 validateStackTrace(event.getStackTrace()); 226 } 227 228 static void testBulkRevocationNoRebias() throws Throwable { 229 class MyLock {}; 230 231 Recording recording = new Recording(); 232 233 recording.enable(EventNames.BiasedLockClassRevocation); 234 recording.start(); 235 236 Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 237 Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 238 239 recording.stop(); 240 List<RecordedEvent> events = getRevocationEvents(recording, "revokedClass", MyLock.class); 241 Asserts.assertEQ(events.size(), 2); 242 243 // The rebias event should occur before the noRebias one 244 RecordedEvent eventRebias = events.get(0); 245 RecordedEvent eventNoRebias = events.get(1); 246 247 Events.assertEventThread(eventRebias, biasBreaker0); 248 Events.assertField(eventRebias, "disableBiasing").equal(false); 249 250 Events.assertEventThread(eventNoRebias, biasBreaker1); 251 Events.assertField(eventNoRebias, "disableBiasing").equal(true); 252 253 RecordedClass lockClassRebias = eventRebias.getValue("revokedClass"); 254 Asserts.assertEquals(lockClassRebias.getName(), MyLock.class.getName()); 255 RecordedClass lockClassNoRebias = eventNoRebias.getValue("revokedClass"); 256 Asserts.assertEquals(lockClassNoRebias.getName(), MyLock.class.getName()); 257 258 validateStackTrace(eventRebias.getStackTrace()); 259 validateStackTrace(eventNoRebias.getStackTrace()); 260 } 261 262 static void testRevocationSafepointIdCorrelation() throws Throwable { 263 class MyLock {}; 264 265 Recording recording = new Recording(); 266 267 recording.enable(EventNames.BiasedLockRevocation); 268 recording.enable(EventNames.BiasedLockClassRevocation); 269 recording.enable(EventNames.ExecuteVMOperation); 270 recording.start(); 271 272 triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 273 274 recording.stop(); 275 List<RecordedEvent> events = Events.fromRecording(recording); 276 277 // Determine which safepoints included single and bulk revocation VM operations 278 Set<Integer> vmOperationsSingle = new HashSet<>(); 279 Set<Integer> vmOperationsBulk = new HashSet<>(); 280 281 for (RecordedEvent event : events) { 282 if (event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) { 283 String operation = event.getValue("operation"); 284 Integer safepointId = event.getValue("safepointId"); 285 286 if (operation.equals("RevokeBias")) { 287 vmOperationsSingle.add(safepointId); 288 } else if (operation.equals("BulkRevokeBias")) { 289 vmOperationsBulk.add(safepointId); 290 } 291 } 292 } 293 294 int revokeCount = 0; 295 int bulkRevokeCount = 0; 296 297 // Match all revoke events to a corresponding VMOperation event 298 for (RecordedEvent event : events) { 299 if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) { 300 Integer safepointId = event.getValue("safepointId"); 301 String lockClass = ((RecordedClass)event.getValue("lockClass")).getName(); 302 if (lockClass.equals(MyLock.class.getName())) { 303 Asserts.assertTrue(vmOperationsSingle.contains(safepointId)); 304 revokeCount++; 305 } 306 } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) { 307 Integer safepointId = event.getValue("safepointId"); 308 String lockClass = ((RecordedClass)event.getValue("revokedClass")).getName(); 309 if (lockClass.toString().equals(MyLock.class.getName())) { 310 Asserts.assertTrue(vmOperationsBulk.contains(safepointId)); 311 bulkRevokeCount++; 312 } 313 } 314 } 315 316 Asserts.assertGT(bulkRevokeCount, 0); 317 Asserts.assertGT(revokeCount, bulkRevokeCount); 318 } 319 }