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 = 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 }