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 package jdk.jfr.event.oldobject;
  26 
  27 import java.time.Instant;
  28 import java.util.List;
  29 
  30 import jdk.jfr.Event;
  31 import jdk.jfr.Recording;
  32 import jdk.jfr.consumer.RecordedClass;
  33 import jdk.jfr.consumer.RecordedClassLoader;
  34 import jdk.jfr.consumer.RecordedEvent;
  35 import jdk.jfr.consumer.RecordedFrame;
  36 import jdk.jfr.consumer.RecordedStackTrace;
  37 import jdk.jfr.consumer.RecordedThread;
  38 import jdk.jfr.internal.test.WhiteBox;
  39 import jdk.test.lib.Asserts;
  40 import jdk.test.lib.jfr.EventNames;
  41 import jdk.test.lib.jfr.Events;
  42 import jdk.test.lib.jfr.TestClassLoader;
  43 
  44 /**
  45  * @test
  46  * @summary The test verifies that an old object sample maintains references to "stale" metadata
  47  * @requires vm.hasJFR
  48  * @requires vm.gc == "null"
  49  * @key jfr
  50  * @modules jdk.jfr/jdk.jfr.internal.test
  51  * @library /test/lib /test/jdk
  52  * @build jdk.jfr.event.oldobject.TestMetadataObject
  53  * @run main/othervm -XX:TLABSize=2k -Xmx16m jdk.jfr.event.oldobject.TestMetadataRetention
  54  */
  55 public final class TestMetadataRetention {
  56     private final static String TEST_PACKAGE = TestMetadataRetention.class.getPackage().getName();
  57     private final static String TEST_CLASS_LOADER_NAME = "JFR TestClassLoader";
  58     private final static String TEST_CLASS_NAME = TEST_PACKAGE + ".TestMetadataObject";
  59     private final static String ALLOCATOR_THREAD_NAME = "TestAllocationThread";
  60 
  61     public static ClassLoader testClassLoader;
  62     public static Object leakObject;
  63     public static Thread allocatorThread;
  64     public static Class<?> testClass;
  65 
  66     static class ChunkRotation extends Event {
  67     }
  68 
  69     public static void main(String[] args) throws Throwable {
  70         WhiteBox.setWriteAllObjectSamples(true);
  71         while (true) {
  72             int failedAttempts = 0;
  73             try (Recording recording = new Recording()) {
  74                 recording.enable(EventNames.OldObjectSample).withStackTrace();
  75                 recording.enable(EventNames.ClassUnload);
  76                 recording.start();
  77 
  78                 // Setup metadata on the Java heap (class, stack trace and thread)
  79                 testClassLoader = new TestClassLoader();
  80                 testClass = testClassLoader.loadClass(TEST_CLASS_NAME);
  81                 allocatorThread = new Thread(TestMetadataRetention::allocateLeak, ALLOCATOR_THREAD_NAME);
  82                 allocatorThread.start();
  83                 allocatorThread.join();
  84 
  85                 // Clear out metadata on the heap
  86                 testClassLoader = null;
  87                 testClass = null;
  88                 allocatorThread = null;
  89 
  90                 // System.gc() will trigger class unloading if -XX:+ExplicitGCInvokesConcurrent
  91                 // is NOT set. If this flag is set G1 will never unload classes on System.gc()
  92                 // and CMS will not guarantee that all semantically dead classes will be
  93                 // unloaded. As far as the "jfr" key guarantees no VM flags are set from the
  94                 // outside it should be enough with System.gc().
  95                 System.gc();
  96 
  97                 // Provoke a chunk rotation, which will flush out ordinary metadata.
  98                 provokeChunkRotation();
  99                 ChunkRotation cr = new ChunkRotation();
 100                 cr.commit();
 101                 recording.stop();
 102 
 103                 List<RecordedEvent> events = Events.fromRecording(recording);
 104                 RecordedEvent chunkRotation = findChunkRotationEvent(events);
 105                 try {
 106                     // Sanity check that class was unloaded
 107                     Events.hasEvent(recording, EventNames.ClassUnload);
 108                     validateClassUnloadEvent(events);
 109                     // Validate that metadata for old object event has survived chunk rotation
 110                     Events.hasEvent(recording, EventNames.OldObjectSample);
 111                     validateOldObjectEvent(events, chunkRotation.getStartTime());
 112                 } catch (Throwable t) {
 113                     t.printStackTrace();
 114                     System.out.println("Number of failed attempts " + ++failedAttempts);
 115                     continue;
 116                 }
 117                 break;
 118             }
 119         }
 120     }
 121 
 122     private static RecordedEvent findChunkRotationEvent(List<RecordedEvent> events) {
 123         for (RecordedEvent e : events)  {
 124             if (e.getEventType().getName().equals(ChunkRotation.class.getName())) {
 125                 return e;
 126             }
 127         }
 128         Asserts.fail("Could not find chunk rotation event");
 129         return null; // Can't happen;
 130     }
 131 
 132     private static void allocateLeak() {
 133         try {
 134             leakObject = testClass.getMethod("startRecurse").invoke(null);
 135         } catch (Exception e) {
 136             System.out.println("Could not allocate memory leak!");
 137             e.printStackTrace();
 138         }
 139     }
 140 
 141     private static void provokeChunkRotation() {
 142         try (Recording r = new Recording()) {
 143             r.start();
 144             r.stop();
 145         }
 146     }
 147 
 148     private static void validateClassUnloadEvent(List<RecordedEvent> events) throws Throwable {
 149         for (RecordedEvent event : events) {
 150             if (event.getEventType().getName().equals(EventNames.ClassUnload)) {
 151                 RecordedClass unloadedClass = event.getValue("unloadedClass");
 152                 if (TEST_CLASS_NAME.equals(unloadedClass.getName())) {
 153                     RecordedClassLoader definingClassLoader = unloadedClass.getClassLoader();
 154                     Asserts.assertEquals(TEST_CLASS_LOADER_NAME, definingClassLoader.getName(), "Expected " + TEST_CLASS_LOADER_NAME + ", got " + definingClassLoader.getType().getName());
 155                     return;
 156                 }
 157             }
 158         }
 159     }
 160 
 161     private static void validateOldObjectEvent(List<RecordedEvent> events, Instant chunkRotation) throws Throwable {
 162         for (RecordedEvent event : events) {
 163             if (event.getEventType().getName().equals(EventNames.OldObjectSample)) {
 164                 // Only check event after the rotation
 165                 if (!event.getStartTime().isBefore(chunkRotation)) {
 166                     System.out.println(event);
 167                     RecordedThread rt = event.getThread();
 168                     if (rt.getJavaName().equals(ALLOCATOR_THREAD_NAME)) {
 169                         RecordedStackTrace s = event.getStackTrace();
 170                         assertStackTrace(s, "startRecurse");
 171                         assertStackTrace(s, "recurse");
 172                         return;
 173                     }
 174                 }
 175             }
 176         }
 177 
 178         Asserts.fail("Did not find an old object event with thread " + ALLOCATOR_THREAD_NAME);
 179     }
 180 
 181     private static void assertStackTrace(RecordedStackTrace stacktrace, final String methodName) {
 182         for (RecordedFrame f : stacktrace.getFrames()) {
 183             if (f.getMethod().getName().equals(methodName)) {
 184                 return;
 185             }
 186         }
 187         Asserts.fail("Could not find class " + methodName + " in stack trace " + stacktrace);
 188     }
 189 }