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.detailed; 26 27 import static jdk.test.lib.Asserts.assertEquals; 28 import static jdk.test.lib.Asserts.assertNotEquals; 29 import static jdk.test.lib.Asserts.assertTrue; 30 31 import java.util.List; 32 import java.util.Random; 33 import java.util.concurrent.ExecutorService; 34 import java.util.concurrent.Executors; 35 import java.util.concurrent.Semaphore; 36 import java.util.concurrent.ThreadFactory; 37 import java.util.concurrent.TimeUnit; 38 39 import jdk.jfr.Recording; 40 import jdk.jfr.consumer.RecordedEvent; 41 import jdk.jfr.consumer.RecordedFrame; 42 import jdk.jfr.consumer.RecordedStackTrace; 43 import jdk.jfr.consumer.RecordedThread; 44 import jdk.test.lib.jfr.EventNames; 45 import jdk.test.lib.jfr.Events; 46 47 /** 48 * Starts several threads which allocate a lot of objects that remain in young 49 * and old generations with defined ratio. Expects event 50 * vm/gc/detailed/allocation_requiring_gc recorded. 51 */ 52 public class StressAllocationGCEvents { 53 54 private Semaphore threadsCompleted; 55 56 public void run(String[] args) throws Exception { 57 threadsCompleted = new Semaphore(0); 58 System.out.println("Total memory= " + Runtime.getRuntime().maxMemory() + " bytes"); 59 60 int obj_size = DEFAULT_OBJ_SIZE; 61 if (args.length > 0) { 62 obj_size = Integer.parseInt(args[0]); 63 } 64 65 System.out.println("Objects size= " + obj_size + " bytes"); 66 ExecutorService executor 67 = Executors.newFixedThreadPool(THREAD_COUNT, new NamedThreadFactory()); 68 Recording r = new Recording(); 69 r.enable(EVENT_NAME_ALLOCATION_REQUIRING_GC); 70 r.start(); 71 72 System.out.println("Starting " + THREAD_COUNT + " threads"); 73 74 for (int i = 0; i < THREAD_COUNT; i++) { 75 executor.execute(new Runner(obj_size)); 76 } 77 78 // Wait for all threads to complete 79 threadsCompleted.acquire(THREAD_COUNT); 80 executor.shutdownNow(); 81 82 if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { 83 System.err.println("Thread pool did not terminate after 10 seconds after shutdown"); 84 } 85 86 r.stop(); 87 88 List<RecordedEvent> allocationEvents 89 = Events.fromRecording(r); 90 91 System.out.println(EVENT_NAME_ALLOCATION_REQUIRING_GC + " " + allocationEvents.size()); 92 93 Events.hasEvents(allocationEvents); 94 95 // check stacktrace depth 96 for (RecordedEvent event : allocationEvents) { 97 checkEvent(event); 98 } 99 } 100 101 class Runner extends Thread { 102 103 public Runner(int obj_size) { 104 this.startTime = System.currentTimeMillis(); 105 this.OBJ_SIZE = obj_size; 106 this.OLD_OBJ_COUNT = Math.max(1, (int) ((float) Runtime.getRuntime().maxMemory() / 2 / THREAD_COUNT / OBJ_SIZE)); 107 this.old_garbage = new Object[OLD_OBJ_COUNT]; 108 109 System.out.println(String.format("In \"%s\" old objects count = %d, recursion depth = %d", 110 this.getName(), OLD_OBJ_COUNT, RECURSION_DEPTH)); 111 } 112 113 @Override 114 public void run() { 115 diver(RECURSION_DEPTH); 116 threadsCompleted.release(); 117 System.out.println("Completed after " + (System.currentTimeMillis() - startTime) + " ms"); 118 } 119 120 private void diver(int stack) { 121 if (stack > 1) { 122 diver(stack - 1); 123 } else { 124 long endTime = startTime + (SECONDS_TO_RUN * 1000); 125 Random r = new Random(startTime); 126 while (endTime > System.currentTimeMillis()) { 127 byte[] garbage = new byte[OBJ_SIZE]; 128 if (r.nextInt(100) > OLD_GEN_RATE) { 129 old_garbage[r.nextInt(OLD_OBJ_COUNT)] = garbage; 130 } 131 } 132 } 133 } 134 135 private final long startTime; 136 private final Object[] old_garbage; 137 private final int OBJ_SIZE; 138 private final int OLD_OBJ_COUNT; 139 } 140 141 ///< check stacktrace depth 142 private void checkEvent(RecordedEvent event) throws Exception { 143 // skip check if allocation failure comes not from diver 144 145 RecordedThread thread = event.getThread(); 146 String threadName = thread.getJavaName(); 147 148 if (!threadName.contains(THREAD_NAME)) { 149 System.out.println("Skip event not from pool (from internals)"); 150 System.out.println(" Thread Id: " + thread.getJavaThreadId() 151 + " Thread name: " + threadName); 152 return; 153 } 154 155 RecordedStackTrace stackTrace = event.getStackTrace(); 156 157 List<RecordedFrame> frames = stackTrace.getFrames(); 158 //String[] stacktrace = StackTraceHelper.buildStackTraceFromFrames(frames); 159 160 if (!(frames.get(0).getMethod().getName().equals(DIVER_FRAME_NAME))) { 161 System.out.println("Skip stacktrace check for: \n" 162 + String.join("\n", threadName)); 163 return; 164 } 165 166 assertTrue(frames.size() > RECURSION_DEPTH, 167 "Stack trace should contain at least one more entry than the ones generated by the test recursion"); 168 for (int i = 0; i < RECURSION_DEPTH; i++) { 169 assertEquals(frames.get(i).getMethod().getName(), DIVER_FRAME_NAME, 170 "Frame " + i + " is wrong: \n" 171 + String.join("\n", threadName)); 172 } 173 assertNotEquals(frames.get(RECURSION_DEPTH).getMethod().getName(), DIVER_FRAME_NAME, 174 "Too many diver frames: \n" 175 + String.join("\n", threadName)); 176 } 177 178 class NamedThreadFactory implements ThreadFactory { 179 180 private int threadNum = 0; 181 182 @Override 183 public Thread newThread(Runnable r) { 184 return new Thread(r, THREAD_NAME + (threadNum++)); 185 } 186 } 187 188 // Because each thread will keep some number of objects live we need to limit 189 // the number of threads to ensure we don't run out of heap space. The big 190 // allocation tests uses 256m heap and 1m allocations, so a 64 thread limit 191 // should be fine. 192 private final static int THREAD_COUNT_LIMIT = 64; 193 private final static int THREAD_COUNT = Math.min(1 + (int) (Runtime.getRuntime().availableProcessors() * 2), THREAD_COUNT_LIMIT); 194 private final static int SECONDS_TO_RUN = 60; 195 private final static int DEFAULT_OBJ_SIZE = 1024; 196 private final static int OLD_GEN_RATE = 60; // from 0 to 100 197 private final static int RECURSION_DEPTH = 5; 198 private final static String EVENT_NAME_ALLOCATION_REQUIRING_GC = EventNames.AllocationRequiringGC; 199 private static final String THREAD_NAME = "JFRTest-"; 200 private static final String DIVER_FRAME_NAME = "StressAllocationGCEvents$Runner.diver"; 201 }