1 /*
  2  * Copyright (c) 2018, 2025, 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.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 package jdk.jfr.event.oldobject;
 25 
 26 import java.lang.reflect.Array;
 27 import java.util.ArrayList;
 28 import java.util.HashSet;
 29 import java.util.List;
 30 import java.util.Set;
 31 import java.util.concurrent.Callable;
 32 
 33 import jdk.jfr.Recording;
 34 import jdk.jfr.consumer.RecordedEvent;
 35 import jdk.jfr.consumer.RecordedObject;
 36 import jdk.jfr.internal.test.WhiteBox;
 37 import jdk.test.lib.jfr.EventNames;
 38 import jdk.test.lib.jfr.Events;
 39 
 40 /**
 41  * @test
 42  * @requires vm.flagless
 43  * @comment Marked as flagless until JDK-8344015 is fixed
 44  * @requires vm.hasJFR
 45  * @requires vm.gc != "Shenandoah"
 46  * @requires vm.opt.final.UseCompactObjectHeaders == false
 47  * @library /test/lib /test/jdk
 48  * @modules jdk.jfr/jdk.jfr.internal.test
 49  * @run main/othervm -XX:TLABSize=2k jdk.jfr.event.oldobject.TestObjectDescription
 50  */
 51 public class TestObjectDescription {
 52 
 53     private static final int OBJECT_DESCRIPTION_MAX_SIZE = 100;
 54     private static final String CLASS_NAME = TestClassLoader.class.getName() + "$TestClass";
 55     public static List<?> leaks;
 56 
 57     public final static class MyThreadGroup extends ThreadGroup {
 58         public final static String NAME = "My Thread Group";
 59 
 60         public MyThreadGroup(String name) {
 61             super(name);
 62         }
 63 
 64         // Allocate array to trigger sampling code path for interpreter / c1
 65         byte[] bytes = new byte[10];
 66     }
 67 
 68     public final static class MyThread extends Thread {
 69         public final static String NAME = "My Thread";
 70 
 71         public MyThread() {
 72             super(NAME);
 73         }
 74 
 75         // Allocate array to trigger sampling code path for interpreter / c1
 76         byte[] bytes = new byte[10];
 77     }
 78 
 79     public static void main(String[] args) throws Exception {
 80         WhiteBox.setWriteAllObjectSamples(true);
 81 
 82         testThreadGroupName();
 83         testThreadName();
 84         testClassName();
 85         testSize();
 86         testEllipsis();
 87     }
 88 
 89     private static void testThreadName() throws Exception {
 90         asseertObjectDescription(() -> {
 91             List<MyThread> threads = new ArrayList<>(OldObjects.MIN_SIZE);
 92             for (int i = 0; i < OldObjects.MIN_SIZE; i++) {
 93                 threads.add(new MyThread());
 94             }
 95             return threads;
 96         }, "Thread Name: " + MyThread.NAME);
 97     }
 98 
 99     private static void testThreadGroupName() throws Exception {
100         asseertObjectDescription(() -> {
101             List<MyThreadGroup> groups = new ArrayList<>(OldObjects.MIN_SIZE);
102             for (int i = 0; i < OldObjects.MIN_SIZE; i++) {
103                 groups.add(new MyThreadGroup("My Thread Group"));
104             }
105             return groups;
106         }, "Thread Group: " + "My Thread Group");
107     }
108 
109     private static void testClassName() throws Exception {
110         asseertObjectDescription(() -> {
111             TestClassLoader testClassLoader = new TestClassLoader();
112             List<Object> classObjects = new ArrayList<>(OldObjects.MIN_SIZE);
113             for (Class<?> clazz : testClassLoader.loadClasses(OldObjects.MIN_SIZE / 20)) {
114                 // Allocate array to trigger sampling code path for interpreter / c1
115                 for (int i = 0; i < 20; i++) {
116                     Object classArray = Array.newInstance(clazz, 20);
117                     Array.set(classArray, i, clazz.newInstance());
118                     classObjects.add(classArray);
119                 }
120             }
121             return classObjects;
122         }, "Class Name: " + CLASS_NAME);
123     }
124 
125     private static void testSize() throws Exception {
126         asseertObjectDescription(() -> {
127             List<Object> arrayLists = new ArrayList<>(OldObjects.MIN_SIZE);
128             for (int i = 0; i < OldObjects.MIN_SIZE; i++) {
129                 List<Object> arrayList = new ArrayList<>();
130                 arrayList.add(new Object());
131                 arrayList.add(new Object());
132                 arrayList.add(new Object());
133                 arrayLists.add(arrayList);
134             }
135             return arrayLists;
136         }, "Size: 3");
137     }
138 
139     private static void testEllipsis() throws Exception {
140         asseertObjectDescription(() -> {
141             StringBuilder sb = new StringBuilder();
142             for (int i = 0; i < 2 * OBJECT_DESCRIPTION_MAX_SIZE; i++) {
143                 sb.append("x");
144             }
145             String threadName = sb.toString();
146             List<Thread> threads = new ArrayList<>();
147             for (int i = 0; i < OldObjects.MIN_SIZE; i++) {
148                 threads.add(new Thread(threadName));
149             }
150             return threads;
151         }, "xxx...");
152     }
153 
154     private static void asseertObjectDescription(Callable<List<?>> callable, String text) throws Exception {
155         int iteration = 1;
156         while (true) {
157             try (Recording recording = new Recording()) {
158                 System.out.println("Iteration: " + iteration);
159                 recording.enable(EventNames.OldObjectSample).withoutStackTrace().with("cutoff", "infinity");
160                 recording.start();
161                 leaks = null;
162                 System.gc();
163                 leaks = callable.call();
164 
165                 recording.stop();
166 
167                 List<RecordedEvent> events = Events.fromRecording(recording);
168                 Set<String> objectDescriptions = extractObjectDecriptions(events);
169                 for (String s : objectDescriptions) {
170                     if (s.contains(text)) {
171                         printDescriptions(objectDescriptions);
172                         return;
173                     }
174                 }
175                 System.out.println("Could not find object description containing text \"" + text + "\"");
176                 printDescriptions(objectDescriptions);
177                 System.out.println();
178                 iteration++;
179             }
180         }
181     }
182 
183     private static void printDescriptions(Set<String> objectDescriptions) {
184         System.out.println("Found descriptions:");
185         for (String t : objectDescriptions) {
186             System.out.println(t);
187         }
188     }
189 
190     private static Set<String> extractObjectDecriptions(List<RecordedEvent> events) {
191         Set<String> objectDescriptions = new HashSet<>();
192         for (RecordedEvent e : events) {
193             objectDescriptions.addAll(extractObjectDescriptions(e.getValue("object")));
194         }
195         return objectDescriptions;
196     }
197 
198     private static Set<String> extractObjectDescriptions(RecordedObject o) {
199         Set<Long> visited = new HashSet<>();
200         Set<String> descriptions = new HashSet<>();
201         while (o != null) {
202             Long memoryAddress = o.getValue("address");
203             if (visited.contains(memoryAddress)) {
204                 return descriptions;
205             }
206             visited.add(memoryAddress);
207             String od = o.getValue("description");
208             if (od != null) {
209                 descriptions.add(od);
210             }
211             RecordedObject referrer = o.getValue("referrer");
212             o = referrer != null ? referrer.getValue("object") : null;
213         }
214         return descriptions;
215     }
216 }