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
24import java.io.PrintStream;
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Map;
28import java.util.Random;
29import 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 main 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 */
56public 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
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     */
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     */
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     */
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
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     */
215    private Object getRandomObject() {
216        int index = (windowStart + RND.nextInt(windowSize)) % N;
217        return STORAGE.get(index);
218    }
219
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         */
242        Worker(TestMultiThreadStressRSet boss, int refsToOldGen) {
243            this.boss = boss;
244            this.refs = refsToOldGen;
245        }
246
247        @Override
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
275        Shifter(TestMultiThreadStressRSet boss, int sleepTime, int shift) {
276            this.boss = boss;
277            this.sleepTime = sleepTime;
278            this.shift = shift;
279        }
280
281        @Override
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