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