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 * 48 * 49 * @key jfr 50 * 51 * @library /lib / 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 = "jdk/test/lib/jfr/TestClassLoader"; // see TestClassLoader.java, was "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 int failedAttempts = 0; 72 while (true) { 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.err.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 }