1 /* 2 * Copyright (c) 2015, 2019, 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 package jdk.jfr.event.gc.stacktrace; 26 27 import javax.management.MBeanServer; 28 import java.lang.management.ManagementFactory; 29 import com.sun.management.GarbageCollectorMXBean; 30 31 import jdk.jfr.Recording; 32 import jdk.jfr.consumer.RecordedEvent; 33 import jdk.jfr.consumer.RecordedStackTrace; 34 import jdk.jfr.consumer.RecordedFrame; 35 import jdk.jfr.consumer.RecordedMethod; 36 import jdk.jfr.consumer.RecordedThread; 37 import jdk.testlibrary.jfr.EventNames; 38 import jdk.testlibrary.jfr.Events; 39 40 import java.util.List; 41 import java.util.ArrayList; 42 43 import java.net.URL; 44 import java.net.URLClassLoader; 45 import java.lang.reflect.InvocationHandler; 46 import java.lang.reflect.Method; 47 import java.lang.reflect.Proxy; 48 49 abstract class MemoryAllocator { 50 51 public static final int KB = 1024; 52 public static final int MB = 1024 * KB; 53 54 public static Object garbage = null; 55 56 abstract public void allocate(); 57 public void clear() { 58 garbage = null; 59 } 60 } 61 62 class EdenMemoryAllocator extends MemoryAllocator { 63 64 @Override 65 public void allocate() { 66 garbage = new byte[10 * KB]; 67 } 68 } 69 70 class HumongousMemoryAllocator extends MemoryAllocator { 71 72 @Override 73 public void allocate() { 74 garbage = new byte[5 * MB]; 75 } 76 } 77 78 /** 79 * Attempts to fill up young gen and allocate in old gen 80 */ 81 class OldGenMemoryAllocator extends MemoryAllocator { 82 83 private List<byte[]> list = new ArrayList<byte[]>(); 84 private int counter = 6000; 85 86 @Override 87 public void allocate() { 88 if (counter-- > 0) { 89 list.add(new byte[10 * KB]); 90 } else { 91 list = new ArrayList<byte[]>(); 92 counter = 6000; 93 } 94 95 garbage = list; 96 } 97 98 @Override 99 public void clear(){ 100 list = null; 101 super.clear(); 102 } 103 } 104 105 class MetaspaceMemoryAllocator extends MemoryAllocator { 106 107 private static int counter = 0; 108 109 /** 110 * Imitates class loading. Each invocation of this method causes a new class 111 * loader object is created and a new class is loaded by this class loader. 112 * Method throws OOM when run out of memory. 113 */ 114 static protected void loadNewClass() { 115 try { 116 String jarUrl = "file:" + (counter++) + ".jar"; 117 URL[] urls = new URL[]{new URL(jarUrl)}; 118 URLClassLoader cl = new URLClassLoader(urls); 119 Proxy.newProxyInstance( 120 cl, 121 new Class[]{Foo.class}, 122 new FooInvocationHandler(new FooBar())); 123 } catch (java.net.MalformedURLException badThing) { 124 // should never occur 125 System.err.println("Unexpected error: " + badThing); 126 throw new RuntimeException(badThing); 127 } 128 } 129 130 @Override 131 public void allocate() { 132 try { 133 loadNewClass(); 134 } catch (OutOfMemoryError e) { 135 /* empty */ 136 } 137 } 138 139 public static interface Foo { 140 } 141 142 public static class FooBar implements Foo { 143 } 144 145 static class FooInvocationHandler implements InvocationHandler { 146 147 private final Foo foo; 148 149 FooInvocationHandler(Foo foo) { 150 this.foo = foo; 151 } 152 153 @Override 154 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 155 return method.invoke(foo, args); 156 } 157 } 158 } 159 160 /** 161 * Utility class to peform JFR recording, GC provocation/detection and 162 * stacktrace verification for related JFR events 163 */ 164 public class AllocationStackTrace { 165 166 /** 167 * Tests event stacktrace for young GC if -XX:+UseSerialGC is used 168 */ 169 public static void testDefNewAllocEvent() throws Exception { 170 GarbageCollectorMXBean bean = garbageCollectorMXBean("Copy"); 171 MemoryAllocator memory = new EdenMemoryAllocator(); 172 173 String[] expectedStack = new String[]{ 174 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 175 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testDefNewAllocEvent" 176 }; 177 178 testAllocEvent(bean, memory, expectedStack); 179 } 180 181 /** 182 * Tests event stacktrace for old GC if -XX:+UseSerialGC is used 183 */ 184 public static void testMarkSweepCompactAllocEvent() throws Exception { 185 GarbageCollectorMXBean bean = garbageCollectorMXBean("MarkSweepCompact"); 186 MemoryAllocator memory = new OldGenMemoryAllocator(); 187 188 String[] expectedStack = new String[]{ 189 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 190 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testMarkSweepCompactAllocEvent" 191 }; 192 193 testAllocEvent(bean, memory, expectedStack); 194 } 195 196 /** 197 * Tests event stacktrace during metaspace GC threshold if -XX:+UseSerialGC 198 * is used 199 */ 200 public static void testMetaspaceSerialGCAllocEvent() throws Exception { 201 GarbageCollectorMXBean bean = garbageCollectorMXBean("MarkSweepCompact"); 202 MemoryAllocator memory = new MetaspaceMemoryAllocator(); 203 204 String[] expectedStack = new String[]{ 205 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 206 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testMetaspaceSerialGCAllocEvent" 207 }; 208 209 testAllocEvent(bean, memory, expectedStack); 210 } 211 212 /** 213 * Tests event stacktrace for young GC if -XX:+UseConcMarkSweepGC is used 214 */ 215 public static void testParNewAllocEvent() throws Exception { 216 GarbageCollectorMXBean bean = garbageCollectorMXBean("ParNew"); 217 MemoryAllocator memory = new EdenMemoryAllocator(); 218 219 String[] expectedStack = new String[]{ 220 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 221 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testParNewAllocEvent" 222 }; 223 224 testAllocEvent(bean, memory, expectedStack); 225 } 226 227 /** 228 * Tests event stacktrace for old GC if -XX:+UseConcMarkSweepGC is used 229 */ 230 public static void testConcMarkSweepAllocEvent() throws Exception { 231 GarbageCollectorMXBean bean = garbageCollectorMXBean("ConcurrentMarkSweep"); 232 MemoryAllocator memory = new OldGenMemoryAllocator(); 233 234 String[] expectedStack = new String[]{ 235 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 236 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testConcMarkSweepAllocEvent" 237 }; 238 239 testAllocEvent(bean, memory, expectedStack); 240 } 241 242 /** 243 * Tests event stacktrace during metaspace GC threshold if 244 * -XX:+UseConcMarkSweepGC is used 245 */ 246 public static void testMetaspaceConcMarkSweepGCAllocEvent() throws Exception { 247 GarbageCollectorMXBean bean = garbageCollectorMXBean("ConcurrentMarkSweep"); 248 MemoryAllocator memory = new MetaspaceMemoryAllocator(); 249 250 String[] expectedStack = new String[]{ 251 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 252 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testMetaspaceConcMarkSweepGCAllocEvent" 253 }; 254 255 testAllocEvent(bean, memory, expectedStack); 256 } 257 258 /** 259 * Tests event stacktrace for young GC if -XX:+UseParallelGC is used 260 */ 261 public static void testParallelScavengeAllocEvent() throws Exception { 262 GarbageCollectorMXBean bean = garbageCollectorMXBean("PS Scavenge"); 263 MemoryAllocator memory = new EdenMemoryAllocator(); 264 265 String[] expectedStack = new String[]{ 266 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 267 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testParallelScavengeAllocEvent" 268 }; 269 270 testAllocEvent(bean, memory, expectedStack); 271 } 272 273 /** 274 * Tests event stacktrace for old GC if -XX:+UseParallelGC is used 275 */ 276 public static void testParallelMarkSweepAllocEvent() throws Exception { 277 GarbageCollectorMXBean bean = garbageCollectorMXBean("PS MarkSweep"); 278 MemoryAllocator memory = new OldGenMemoryAllocator(); 279 280 String[] expectedStack = new String[]{ 281 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 282 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testParallelMarkSweepAllocEvent" 283 }; 284 285 testAllocEvent(bean, memory, expectedStack); 286 } 287 288 /** 289 * Tests event stacktrace during metaspace GC threshold if 290 * -XX:+UseParallelGC is used 291 */ 292 public static void testMetaspaceParallelGCAllocEvent() throws Exception { 293 GarbageCollectorMXBean bean = garbageCollectorMXBean("PS MarkSweep"); 294 MemoryAllocator memory = new MetaspaceMemoryAllocator(); 295 296 String[] expectedStack = new String[]{ 297 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 298 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testMetaspaceParallelGCAllocEvent" 299 }; 300 301 testAllocEvent(bean, memory, expectedStack); 302 } 303 304 /** 305 * Tests event stacktrace for young GC if -XX:+UseG1GC is used 306 */ 307 public static void testG1YoungAllocEvent() throws Exception { 308 GarbageCollectorMXBean bean = garbageCollectorMXBean("G1 Young Generation"); 309 MemoryAllocator memory = new EdenMemoryAllocator(); 310 311 String[] expectedStack = new String[]{ 312 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 313 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testG1YoungAllocEvent" 314 }; 315 316 testAllocEvent(bean, memory, expectedStack); 317 } 318 319 /** 320 * Tests event stacktrace for old GC if -XX:+UseG1GC is used 321 */ 322 public static void testG1OldAllocEvent() throws Exception { 323 GarbageCollectorMXBean bean = garbageCollectorMXBean("G1 Old Generation"); 324 MemoryAllocator memory = new OldGenMemoryAllocator(); 325 326 String[] expectedStack = new String[]{ 327 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 328 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testG1OldAllocEvent" 329 }; 330 331 testAllocEvent(bean, memory, expectedStack); 332 } 333 334 /** 335 * Tests event stacktrace during metaspace GC threshold if -XX:+UseG1GC is 336 * used 337 */ 338 public static void testMetaspaceG1GCAllocEvent() throws Exception { 339 GarbageCollectorMXBean bean = garbageCollectorMXBean("G1 Young Generation"); 340 MemoryAllocator memory = new MetaspaceMemoryAllocator(); 341 342 String[] expectedStack = new String[]{ 343 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 344 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testMetaspaceG1GCAllocEvent" 345 }; 346 347 testAllocEvent(bean, memory, expectedStack); 348 } 349 350 /** 351 * Tests event stacktrace for GC caused by humongous allocations if 352 * -XX:+UseG1GC is used 353 */ 354 public static void testG1HumonAllocEvent() throws Exception { 355 // G1 tries to reclaim humongous objects at every young collection 356 // after doing a conservative estimate of its liveness 357 GarbageCollectorMXBean bean = garbageCollectorMXBean("G1 Young Generation"); 358 MemoryAllocator memory = new HumongousMemoryAllocator(); 359 360 String[] expectedStack = new String[]{ 361 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testAllocEvent", 362 "jdk.jfr.event.gc.stacktrace.AllocationStackTrace.testG1HumonAllocEvent" 363 }; 364 365 testAllocEvent(bean, memory, expectedStack); 366 } 367 368 private static GarbageCollectorMXBean garbageCollectorMXBean(String name) throws Exception { 369 MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 370 GarbageCollectorMXBean bean = ManagementFactory.newPlatformMXBeanProxy( 371 server, "java.lang:type=GarbageCollector,name=" + name, GarbageCollectorMXBean.class); 372 return bean; 373 } 374 375 /** 376 * Performs JFR recording, GC provocation/detection and stacktrace 377 * verification for JFR event. In case of verification failure 378 * repeats several times. 379 * 380 * @param bean MX bean for desired GC 381 * @param memory allocator for desired type of allocations 382 * @param expectedStack array of expected frames 383 */ 384 private static void testAllocEvent(GarbageCollectorMXBean bean, MemoryAllocator memory, String[] expectedStack) throws Exception { 385 // The test checks the stacktrace of events and expects all the events are fired 386 // in the current thread. But compilation may also trigger GC. 387 // So to filter out such cases the test performs several iterations and expects 388 // that the memory allocations made by the test will produce the desired JFR event. 389 final int iterations = 5; 390 for (int i = 0; i < iterations; i++) { 391 if (allocAndCheck(bean, memory, expectedStack)) { 392 return; 393 } else { 394 System.out.println("Attempt: " + i + " out of " + iterations+": no matching stack trace found."); 395 } 396 memory.clear(); 397 } 398 throw new AssertionError("No matching stack trace found"); 399 } 400 401 /** 402 * Performs JFR recording, GC provocation/detection and stacktrace 403 * verification for JFR event. 404 * 405 * @param bean MX bean for desired GC 406 * @param memory allocator for desired type of allocations 407 * @param expectedStack array of expected frames 408 * @throws Exception 409 */ 410 private static boolean allocAndCheck(GarbageCollectorMXBean bean, MemoryAllocator memory, 411 String[] expectedStack) throws Exception { 412 String threadName = Thread.currentThread().getName(); 413 String event = EventNames.AllocationRequiringGC; 414 415 Recording r = new Recording(); 416 r.enable(event).withStackTrace(); 417 r.start(); 418 419 long prevCollectionCount = bean.getCollectionCount(); 420 long collectionCount = -1; 421 422 long iterationCount = 0; 423 424 do { 425 memory.allocate(); 426 collectionCount = bean.getCollectionCount(); 427 iterationCount++; 428 } while (collectionCount == prevCollectionCount); 429 430 System.out.println("Allocation num: " + iterationCount); 431 System.out.println("GC detected: " + collectionCount); 432 433 r.stop(); 434 List<RecordedEvent> events = Events.fromRecording(r); 435 436 System.out.println("JFR GC events found: " + events.size()); 437 438 // Find any event that matched the expected stack trace 439 for (RecordedEvent e : events) { 440 System.out.println("Event: " + e); 441 RecordedThread thread = e.getThread(); 442 String eventThreadName = thread.getJavaName(); 443 if (!threadName.equals(eventThreadName)) { 444 continue; 445 } 446 if (matchingStackTrace(e.getStackTrace(), expectedStack)) { 447 return true; 448 } 449 } 450 return false; 451 } 452 453 private static boolean matchingStackTrace(RecordedStackTrace stack, String[] expectedStack) { 454 if (stack == null) { 455 return false; 456 } 457 458 List<RecordedFrame> frames = stack.getFrames(); 459 int pos = findFramePos(frames, expectedStack[0]); 460 461 if (pos == -1) { 462 return false; 463 } 464 465 for (String expectedFrame : expectedStack) { 466 RecordedFrame f = frames.get(pos++); 467 String frame = frameToString(f); 468 469 if (!frame.equals(expectedFrame)) { 470 return false; 471 } 472 } 473 474 return true; 475 } 476 477 private static int findFramePos(List<RecordedFrame> frames, String frame) { 478 int pos = 0; 479 480 for (RecordedFrame f : frames) { 481 if (frame.equals(frameToString(f))) { 482 return pos; 483 } 484 pos++; 485 } 486 487 return -1; 488 } 489 490 private static String frameToString(RecordedFrame f) { 491 RecordedMethod m = f.getMethod(); 492 String methodName = m.getName(); 493 String className = m.getType().getName(); 494 return className + "." + methodName; 495 } 496 497 }