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