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.HashMap; 37 import java.util.List; 38 import java.util.concurrent.FutureTask; 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 static void testSingleRevocation() throws Throwable { 103 class MyLock {}; 104 105 Recording recording = new Recording(); 106 107 recording.enable(EventNames.BiasedLockRevocation); 108 recording.start(); 109 110 Thread biasBreaker = triggerRevocation(1, MyLock.class); 111 112 recording.stop(); 113 114 List<RecordedEvent> events = Events.fromRecording(recording); 115 116 // We may or may not catch a second revocation from the biasBreaker thread exiting 117 Asserts.assertGreaterThanOrEqual(events.size(), 1); 118 119 RecordedEvent event = events.get(0); 120 Events.assertEventThread(event, biasBreaker); 121 Events.assertEventThread(event, "previousOwner", Thread.currentThread()); 122 123 RecordedClass lockClass = event.getValue("lockClass"); 124 Asserts.assertEquals(lockClass.getName(), MyLock.class.getName()); 125 126 validateStackTrace(event.getStackTrace()); 127 } 128 129 static void testBulkRevocation() throws Throwable { 130 class MyLock {}; 131 132 Recording recording = new Recording(); 133 134 recording.enable(EventNames.BiasedLockClassRevocation); 135 recording.start(); 136 137 Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 138 139 recording.stop(); 140 List<RecordedEvent> events = Events.fromRecording(recording); 141 Asserts.assertEQ(events.size(), 1); 142 143 RecordedEvent event = events.get(0); 144 Events.assertEventThread(event, biasBreaker); 145 Events.assertField(event, "disableBiasing").equal(false); 146 147 RecordedClass lockClass = event.getValue("revokedClass"); 148 Asserts.assertEquals(lockClass.getName(), MyLock.class.getName()); 149 150 validateStackTrace(event.getStackTrace()); 151 } 152 153 static void testSelfRevocation() throws Throwable { 154 class MyLock {}; 155 156 Recording recording = new Recording(); 157 158 recording.enable(EventNames.BiasedLockSelfRevocation); 159 recording.start(); 160 161 MyLock l = new MyLock(); 162 touch(l); 163 Thread.holdsLock(l); 164 165 recording.stop(); 166 167 List<RecordedEvent> events = Events.fromRecording(recording); 168 Asserts.assertEQ(events.size(), 1); 169 170 RecordedEvent event = events.get(0); 171 Events.assertEventThread(event, Thread.currentThread()); 172 173 validateStackTrace(event.getStackTrace(), "holdsLock"); 174 } 175 176 static void testExitedThreadRevocation() throws Throwable { 177 class MyLock {}; 178 179 Recording recording = new Recording(); 180 181 recording.enable(EventNames.BiasedLockRevocation); 182 recording.start(); 183 184 FutureTask<MyLock> lockerTask = new FutureTask<>(() -> { 185 MyLock l = new MyLock(); 186 touch(l); 187 return l; 188 }); 189 190 Thread locker = new Thread(lockerTask, "BiasLocker"); 191 locker.start(); 192 locker.join(); 193 194 // Even after joining, the VM has a bit more work to do before the thread is actually removed 195 // from the threads list. Ensure that this has happened before proceeding. 196 while (true) { 197 PidJcmdExecutor jcmd = new PidJcmdExecutor(); 198 OutputAnalyzer oa = jcmd.execute("Thread.print", true); 199 String lockerThreadFound = oa.firstMatch("BiasLocker"); 200 if (lockerThreadFound == null) { 201 break; 202 } 203 }; 204 205 MyLock l = lockerTask.get(); 206 touch(l); 207 208 recording.stop(); 209 List<RecordedEvent> events = Events.fromRecording(recording); 210 Events.hasEvents(events); 211 212 // Joining the locker thread can cause revocations as well, search for the interesting one 213 for (RecordedEvent event : events) { 214 RecordedClass lockClass = event.getValue("lockClass"); 215 if (lockClass.getName().equals(MyLock.class.getName())) { 216 Events.assertEventThread(event, Thread.currentThread()); 217 // Previous owner will usually be null, but can also be a thread that 218 // was created after the BiasLocker thread exited due to address reuse. 219 RecordedThread prevOwner = event.getValue("previousOwner"); 220 if (prevOwner != null) { 221 Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker"); 222 } 223 validateStackTrace(event.getStackTrace()); 224 return; 225 } 226 } 227 Asserts.fail("Did not find any revocation event for MyLock"); 228 } 229 230 static void testBulkRevocationNoRebias() throws Throwable { 231 class MyLock {}; 232 233 Recording recording = new Recording(); 234 235 recording.enable(EventNames.BiasedLockClassRevocation); 236 recording.start(); 237 238 Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 239 Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class); 240 241 recording.stop(); 242 List<RecordedEvent> events = Events.fromRecording(recording); 243 Asserts.assertEQ(events.size(), 2); 244 245 RecordedEvent eventRebias = events.get(0); 246 Events.assertEventThread(eventRebias, biasBreaker0); 247 Events.assertField(eventRebias, "disableBiasing").equal(false); 248 249 RecordedEvent eventNoRebias = events.get(1); 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 // Find all biased locking related VMOperation events 278 HashMap<Integer, RecordedEvent> vmOperations = new HashMap<Integer, RecordedEvent>(); 279 for (RecordedEvent event : events) { 280 if ((event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) && 281 (event.getValue("operation").toString().contains("Bias"))) { 282 vmOperations.put(event.getValue("safepointId"), event); 283 } 284 } 285 286 int revokeCount = 0; 287 int bulkRevokeCount = 0; 288 289 // Match all revoke events to a corresponding VMOperation event 290 for (RecordedEvent event : events) { 291 if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) { 292 RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId")); 293 if (event.getValue("safepointId").toString().equals("-1")) { 294 Asserts.assertEquals(vmOpEvent, null); 295 } else { 296 Events.assertField(vmOpEvent, "operation").equal("RevokeBias"); 297 revokeCount++; 298 } 299 } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) { 300 RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId")); 301 Events.assertField(vmOpEvent, "operation").equal("BulkRevokeBias"); 302 bulkRevokeCount++; 303 } 304 } 305 306 // All VMOperations should have had a matching revoke event 307 Asserts.assertEQ(vmOperations.size(), 0); 308 309 Asserts.assertGT(bulkRevokeCount, 0); 310 Asserts.assertGT(revokeCount, bulkRevokeCount); 311 } 312 }