1 /* 2 * Copyright (c) 2015, 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 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.test.lib.jfr.EventNames; 38 import jdk.test.lib.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 = g1YoungGarbageCollectorMXBean(); 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/full GC if -XX:+UseG1GC is used 321 */ 322 public static void testG1OldAllocEvent() throws Exception { 323 GarbageCollectorMXBean bean = g1FullGarbageCollectorMXBean(); 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 = g1YoungGarbageCollectorMXBean(); 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 = g1YoungGarbageCollectorMXBean(); 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; 371 try { 372 bean = ManagementFactory.newPlatformMXBeanProxy(server, 373 "java.lang:type=GarbageCollector,name=" + name, 374 GarbageCollectorMXBean.class); 375 } catch (IllegalArgumentException e) { 376 bean = null; 377 } 378 return bean; 379 } 380 381 private static GarbageCollectorMXBean g1YoungGarbageCollectorMXBean() throws Exception { 382 GarbageCollectorMXBean bean = garbageCollectorMXBean("G1 Young Generation"); 383 if (bean == null) { 384 bean = garbageCollectorMXBean("G1 Young"); 385 } 386 return bean; 387 } 388 389 private static GarbageCollectorMXBean g1FullGarbageCollectorMXBean() throws Exception { 390 GarbageCollectorMXBean bean = garbageCollectorMXBean("G1 Old Generation"); 391 if (bean == null) { 392 bean = garbageCollectorMXBean("G1 Full"); 393 } 394 return bean; 395 } 396 397 /** 398 * Performs JFR recording, GC provocation/detection and stacktrace 399 * verification for JFR event. In case of verification failure 400 * repeats several times. 401 * 402 * @param bean MX bean for desired GC 403 * @param memory allocator for desired type of allocations 404 * @param expectedStack array of expected frames 405 */ 406 private static void testAllocEvent(GarbageCollectorMXBean bean, MemoryAllocator memory, String[] expectedStack) throws Exception { 407 // The test checks the stacktrace of events and expects all the events are fired 408 // in the current thread. But compilation may also trigger GC. 409 // So to filter out such cases the test performs several iterations and expects 410 // that the memory allocations made by the test will produce the desired JFR event. 411 final int iterations = 5; 412 for (int i = 0; i < iterations; i++) { 413 if (allocAndCheck(bean, memory, expectedStack)) { 414 return; 415 } else { 416 System.out.println("Attempt: " + i + " out of " + iterations+": no matching stack trace found."); 417 } 418 memory.clear(); 419 } 420 throw new AssertionError("No matching stack trace found"); 421 } 422 423 /** 424 * Performs JFR recording, GC provocation/detection and stacktrace 425 * verification for JFR event. 426 * 427 * @param bean MX bean for desired GC 428 * @param memory allocator for desired type of allocations 429 * @param expectedStack array of expected frames 430 * @throws Exception 431 */ 432 private static boolean allocAndCheck(GarbageCollectorMXBean bean, MemoryAllocator memory, 433 String[] expectedStack) throws Exception { 434 String threadName = Thread.currentThread().getName(); 435 String event = EventNames.AllocationRequiringGC; 436 437 Recording r = new Recording(); 438 r.enable(event).withStackTrace(); 439 r.start(); 440 441 long prevCollectionCount = bean.getCollectionCount(); 442 long collectionCount = -1; 443 444 long iterationCount = 0; 445 446 do { 447 memory.allocate(); 448 collectionCount = bean.getCollectionCount(); 449 iterationCount++; 450 } while (collectionCount == prevCollectionCount); 451 452 System.out.println("Allocation num: " + iterationCount); 453 System.out.println("GC detected: " + collectionCount); 454 455 r.stop(); 456 List<RecordedEvent> events = Events.fromRecording(r); 457 458 System.out.println("JFR GC events found: " + events.size()); 459 460 // Find any event that matched the expected stack trace 461 for (RecordedEvent e : events) { 462 System.out.println("Event: " + e); 463 RecordedThread thread = e.getThread(); 464 String eventThreadName = thread.getJavaName(); 465 if (!threadName.equals(eventThreadName)) { 466 continue; 467 } 468 if (matchingStackTrace(e.getStackTrace(), expectedStack)) { 469 return true; 470 } 471 } 472 return false; 473 } 474 475 private static boolean matchingStackTrace(RecordedStackTrace stack, String[] expectedStack) { 476 if (stack == null) { 477 return false; 478 } 479 480 List<RecordedFrame> frames = stack.getFrames(); 481 int pos = findFramePos(frames, expectedStack[0]); 482 483 if (pos == -1) { 484 return false; 485 } 486 487 for (String expectedFrame : expectedStack) { 488 RecordedFrame f = frames.get(pos++); 489 String frame = frameToString(f); 490 491 if (!frame.equals(expectedFrame)) { 492 return false; 493 } 494 } 495 496 return true; 497 } 498 499 private static int findFramePos(List<RecordedFrame> frames, String frame) { 500 int pos = 0; 501 502 for (RecordedFrame f : frames) { 503 if (frame.equals(frameToString(f))) { 504 return pos; 505 } 506 pos++; 507 } 508 509 return -1; 510 } 511 512 private static String frameToString(RecordedFrame f) { 513 RecordedMethod m = f.getMethod(); 514 String methodName = m.getName(); 515 String className = m.getType().getName(); 516 return className + "." + methodName; 517 } 518 519 }