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