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