1 /* 2 * Copyright (c) 2016, 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 import java.io.PrintStream; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Map; 28 import java.util.Random; 29 import sun.hotspot.WhiteBox; 30 31 /* 32 * @test TestMultiThreadStressRSet.java 33 * @key stress 34 * @requires vm.gc.G1 35 * @requires os.maxMemory > 2G 36 * @requires vm.opt.MaxGCPauseMillis == "null" 37 * 38 * @summary Stress G1 Remembered Set using multiple threads 39 * @modules java.base/jdk.internal.misc 40 * @library /test/lib 41 * @build sun.hotspot.WhiteBox 42 * @run driver ClassFileInstaller sun.hotspot.WhiteBox 43 * sun.hotspot.WhiteBox$WhiteBoxPermission 44 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 45 * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=1 -Xlog:gc 46 * -Xmx500m -XX:G1HeapRegionSize=1m -XX:MaxGCPauseMillis=1000 TestMultiThreadStressRSet 10 4 47 * 48 * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 49 * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc 50 * -Xmx1G -XX:G1HeapRegionSize=8m -XX:MaxGCPauseMillis=1000 TestMultiThreadStressRSet 60 16 51 * 52 * @run main/othervm/timeout=700 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI 53 * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc 54 * -Xmx500m -XX:G1HeapRegionSize=1m -XX:MaxGCPauseMillis=1000 TestMultiThreadStressRSet 600 32 55 */ 56 public class TestMultiThreadStressRSet { 57 58 private static final Random RND = new Random(2015 * 2016); 59 private static final WhiteBox WB = WhiteBox.getWhiteBox(); 60 private static final int REF_SIZE = WB.getHeapOopSize(); 61 private static final int REGION_SIZE = WB.g1RegionSize(); 62 63 // How many regions to use for the storage 64 private static final int STORAGE_REGIONS = 20; 65 66 // Size a single obj in the storage 67 private static final int OBJ_SIZE = 1024; 68 69 // How many regions of young/old gen to use in the BUFFER 70 private static final int BUFFER_YOUNG_REGIONS = 60; 71 private static final int BUFFER_OLD_REGIONS = 40; 72 73 // Total number of objects in the storage. 74 private final int N; 75 76 // The storage of byte[] 77 private final List<Object> STORAGE; 78 79 // Where references to the Storage will be stored 80 private final List<Object[]> BUFFER; 81 82 // The length of a buffer element. 83 // RSet deals with "cards" (areas of 512 bytes), not with single refs 84 // So, to affect the RSet the BUFFER refs should be allocated in different 85 // memory cards. 86 private final int BUF_ARR_LEN = 100 * (512 / REF_SIZE); 87 88 // Total number of objects in the young/old buffers 89 private final int YOUNG; 90 private final int OLD; 91 92 // To cause Remembered Sets change their coarse level the test uses a window 93 // within STORAGE. All the BUFFER elements refer to only STORAGE objects 94 // from the current window. The window is defined by a range. 95 // The first element has got the index: 'windowStart', 96 // the last one: 'windowStart + windowSize - 1' 97 // The window is shifting periodically. 98 private int windowStart; 99 private final int windowSize; 100 101 // Counter of created worker threads 102 private int counter = 0; 103 104 private volatile String errorMessage = null; 105 private volatile boolean isEnough = false; 106 main(String args[])107 public static void main(String args[]) { 108 if (args.length != 2) { 109 throw new IllegalArgumentException("TEST BUG: wrong arg count " + args.length); 110 } 111 long time = Long.parseLong(args[0]); 112 int threads = Integer.parseInt(args[1]); 113 new TestMultiThreadStressRSet().test(time * 1000, threads); 114 } 115 116 /** 117 * Initiates test parameters, fills out the STORAGE and BUFFER. 118 */ TestMultiThreadStressRSet()119 public TestMultiThreadStressRSet() { 120 121 N = (REGION_SIZE - 1) * STORAGE_REGIONS / OBJ_SIZE + 1; 122 STORAGE = new ArrayList<>(N); 123 int bytes = OBJ_SIZE - 20; 124 for (int i = 0; i < N - 1; i++) { 125 STORAGE.add(new byte[bytes]); 126 } 127 STORAGE.add(new byte[REGION_SIZE / 2 + 100]); // humongous 128 windowStart = 0; 129 windowSize = REGION_SIZE / OBJ_SIZE; 130 131 BUFFER = new ArrayList<>(); 132 int sizeOfBufferObject = 20 + REF_SIZE * BUF_ARR_LEN; 133 OLD = REGION_SIZE * BUFFER_OLD_REGIONS / sizeOfBufferObject; 134 YOUNG = REGION_SIZE * BUFFER_YOUNG_REGIONS / sizeOfBufferObject; 135 for (int i = 0; i < OLD + YOUNG; i++) { 136 BUFFER.add(new Object[BUF_ARR_LEN]); 137 } 138 } 139 140 /** 141 * Does the testing. Steps: 142 * <ul> 143 * <li> starts the Shifter thread 144 * <li> during the given time starts new Worker threads, keeping the number 145 * of live thread under limit. 146 * <li> stops the Shifter thread 147 * </ul> 148 * 149 * @param timeInMillis how long to stress 150 * @param maxThreads the maximum number of Worker thread working together. 151 */ test(long timeInMillis, int maxThreads)152 public void test(long timeInMillis, int maxThreads) { 153 if (timeInMillis <= 0 || maxThreads <= 0) { 154 throw new IllegalArgumentException("TEST BUG: be positive!"); 155 } 156 System.out.println("%% Time to work: " + timeInMillis / 1000 + "s"); 157 System.out.println("%% Number of threads: " + maxThreads); 158 long finish = System.currentTimeMillis() + timeInMillis; 159 Shifter shift = new Shifter(this, 1000, (int) (windowSize * 0.9)); 160 shift.start(); 161 for (int i = 0; i < maxThreads; i++) { 162 new Worker(this, 100).start(); 163 } 164 try { 165 while (System.currentTimeMillis() < finish && errorMessage == null) { 166 Thread.sleep(100); 167 } 168 } catch (Throwable t) { 169 printAllStackTraces(System.err); 170 t.printStackTrace(System.err); 171 this.errorMessage = t.getMessage(); 172 } finally { 173 isEnough = true; 174 } 175 System.out.println("%% Total work cycles: " + counter); 176 if (errorMessage != null) { 177 throw new RuntimeException(errorMessage); 178 } 179 } 180 181 /** 182 * Returns an element from from the BUFFER (an object array) to keep 183 * references to the storage. 184 * 185 * @return an Object[] from buffer. 186 */ getFromBuffer()187 private Object[] getFromBuffer() { 188 int index = counter % (OLD + YOUNG); 189 synchronized (BUFFER) { 190 if (index < OLD) { 191 if (counter % 100 == (counter / 100) % 100) { 192 // need to generate garbage in the old gen to provoke mixed GC 193 return replaceInBuffer(index); 194 } else { 195 return BUFFER.get(index); 196 } 197 } else { 198 return replaceInBuffer(index); 199 } 200 } 201 } 202 replaceInBuffer(int index)203 private Object[] replaceInBuffer(int index) { 204 Object[] objs = new Object[BUF_ARR_LEN]; 205 BUFFER.set(index, objs); 206 return objs; 207 } 208 209 /** 210 * Returns a random object from the current window within the storage. 211 * A storage element with index from windowStart to windowStart+windowSize. 212 * 213 * @return a random element from the current window within the storage. 214 */ getRandomObject()215 private Object getRandomObject() { 216 int index = (windowStart + RND.nextInt(windowSize)) % N; 217 return STORAGE.get(index); 218 } 219 printAllStackTraces(PrintStream ps)220 private static void printAllStackTraces(PrintStream ps) { 221 Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces(); 222 for (Thread t : traces.keySet()) { 223 ps.println(t.toString() + " " + t.getState()); 224 for (StackTraceElement traceElement : traces.get(t)) { 225 ps.println("\tat " + traceElement); 226 } 227 } 228 } 229 230 /** 231 * Thread to create a number of references from BUFFER to STORAGE. 232 */ 233 private static class Worker extends Thread { 234 235 final TestMultiThreadStressRSet boss; 236 final int refs; // number of refs to OldGen 237 238 /** 239 * @param boss the tests 240 * @param refsToOldGen how many references to the OldGen to create 241 */ Worker(TestMultiThreadStressRSet boss, int refsToOldGen)242 Worker(TestMultiThreadStressRSet boss, int refsToOldGen) { 243 this.boss = boss; 244 this.refs = refsToOldGen; 245 } 246 247 @Override run()248 public void run() { 249 try { 250 while (!boss.isEnough) { 251 Object[] objs = boss.getFromBuffer(); 252 int step = objs.length / refs; 253 for (int i = 0; i < refs; i += step) { 254 objs[i] = boss.getRandomObject(); 255 } 256 boss.counter++; 257 } 258 } catch (Throwable t) { 259 t.printStackTrace(System.out); 260 boss.errorMessage = t.getMessage(); 261 } 262 } 263 } 264 265 /** 266 * Periodically shifts the current STORAGE window, removing references 267 * in BUFFER that refer to objects outside the window. 268 */ 269 private static class Shifter extends Thread { 270 271 final TestMultiThreadStressRSet boss; 272 final int sleepTime; 273 final int shift; 274 Shifter(TestMultiThreadStressRSet boss, int sleepTime, int shift)275 Shifter(TestMultiThreadStressRSet boss, int sleepTime, int shift) { 276 this.boss = boss; 277 this.sleepTime = sleepTime; 278 this.shift = shift; 279 } 280 281 @Override run()282 public void run() { 283 try { 284 while (!boss.isEnough) { 285 Thread.sleep(sleepTime); 286 boss.windowStart += shift; 287 for (int i = 0; i < boss.OLD; i++) { 288 Object[] objs = boss.BUFFER.get(i); 289 for (int j = 0; j < objs.length; j++) { 290 objs[j] = null; 291 } 292 } 293 if (!WB.g1InConcurrentMark()) { 294 System.out.println("%% start CMC"); 295 WB.g1StartConcMarkCycle(); 296 } else { 297 System.out.println("%% CMC is already in progress"); 298 } 299 } 300 } catch (Throwable t) { 301 t.printStackTrace(System.out); 302 boss.errorMessage = t.getMessage(); 303 } 304 } 305 } 306 } 307 308