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