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  *   -Xmx1G -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 
--- EOF ---