/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; import sun.hotspot.WhiteBox; /* * @test TestMultiThreadStressRSet.java * @key stress * @requires vm.gc.G1 * @requires os.maxMemory > 2G * @requires vm.opt.MaxGCPauseMillis == "null" * * @summary Stress G1 Remembered Set using multiple threads * @modules java.base/jdk.internal.misc * @library /test/lib * @build sun.hotspot.WhiteBox * @run main ClassFileInstaller sun.hotspot.WhiteBox * sun.hotspot.WhiteBox$WhiteBoxPermission * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=1 -Xlog:gc * -Xmx500m -XX:G1HeapRegionSize=1m -XX:MaxGCPauseMillis=1000 TestMultiThreadStressRSet 10 4 * * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc * -Xmx1G -XX:G1HeapRegionSize=8m -XX:MaxGCPauseMillis=1000 TestMultiThreadStressRSet 60 16 * * @run main/othervm/timeout=700 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc * -Xmx500m -XX:G1HeapRegionSize=1m -XX:MaxGCPauseMillis=1000 TestMultiThreadStressRSet 600 32 */ public class TestMultiThreadStressRSet { private static final Random RND = new Random(2015 * 2016); private static final WhiteBox WB = WhiteBox.getWhiteBox(); private static final int REF_SIZE = WB.getHeapOopSize(); private static final int REGION_SIZE = WB.g1RegionSize(); // How many regions to use for the storage private static final int STORAGE_REGIONS = 20; // Size a single obj in the storage private static final int OBJ_SIZE = 1024; // How many regions of young/old gen to use in the BUFFER private static final int BUFFER_YOUNG_REGIONS = 60; private static final int BUFFER_OLD_REGIONS = 40; // Total number of objects in the storage. private final int N; // The storage of byte[] private final List STORAGE; // Where references to the Storage will be stored private final List BUFFER; // The length of a buffer element. // RSet deals with "cards" (areas of 512 bytes), not with single refs // So, to affect the RSet the BUFFER refs should be allocated in different // memory cards. private final int BUF_ARR_LEN = 100 * (512 / REF_SIZE); // Total number of objects in the young/old buffers private final int YOUNG; private final int OLD; // To cause Remembered Sets change their coarse level the test uses a window // within STORAGE. All the BUFFER elements refer to only STORAGE objects // from the current window. The window is defined by a range. // The first element has got the index: 'windowStart', // the last one: 'windowStart + windowSize - 1' // The window is shifting periodically. private int windowStart; private final int windowSize; // Counter of created worker threads private int counter = 0; private volatile String errorMessage = null; private volatile boolean isEnough = false; public static void main(String args[]) { if (args.length != 2) { throw new IllegalArgumentException("TEST BUG: wrong arg count " + args.length); } long time = Long.parseLong(args[0]); int threads = Integer.parseInt(args[1]); new TestMultiThreadStressRSet().test(time * 1000, threads); } /** * Initiates test parameters, fills out the STORAGE and BUFFER. */ public TestMultiThreadStressRSet() { N = (REGION_SIZE - 1) * STORAGE_REGIONS / OBJ_SIZE + 1; STORAGE = new ArrayList<>(N); int bytes = OBJ_SIZE - 20; for (int i = 0; i < N - 1; i++) { STORAGE.add(new byte[bytes]); } STORAGE.add(new byte[REGION_SIZE / 2 + 100]); // humongous windowStart = 0; windowSize = REGION_SIZE / OBJ_SIZE; BUFFER = new ArrayList<>(); int sizeOfBufferObject = 20 + REF_SIZE * BUF_ARR_LEN; OLD = REGION_SIZE * BUFFER_OLD_REGIONS / sizeOfBufferObject; YOUNG = REGION_SIZE * BUFFER_YOUNG_REGIONS / sizeOfBufferObject; for (int i = 0; i < OLD + YOUNG; i++) { BUFFER.add(new Object[BUF_ARR_LEN]); } } /** * Does the testing. Steps: * * * @param timeInMillis how long to stress * @param maxThreads the maximum number of Worker thread working together. */ public void test(long timeInMillis, int maxThreads) { if (timeInMillis <= 0 || maxThreads <= 0) { throw new IllegalArgumentException("TEST BUG: be positive!"); } System.out.println("%% Time to work: " + timeInMillis / 1000 + "s"); System.out.println("%% Number of threads: " + maxThreads); long finish = System.currentTimeMillis() + timeInMillis; Shifter shift = new Shifter(this, 1000, (int) (windowSize * 0.9)); shift.start(); for (int i = 0; i < maxThreads; i++) { new Worker(this, 100).start(); } try { while (System.currentTimeMillis() < finish && errorMessage == null) { Thread.sleep(100); } } catch (Throwable t) { printAllStackTraces(System.err); t.printStackTrace(System.err); this.errorMessage = t.getMessage(); } finally { isEnough = true; } System.out.println("%% Total work cycles: " + counter); if (errorMessage != null) { throw new RuntimeException(errorMessage); } } /** * Returns an element from from the BUFFER (an object array) to keep * references to the storage. * * @return an Object[] from buffer. */ private Object[] getFromBuffer() { int index = counter % (OLD + YOUNG); synchronized (BUFFER) { if (index < OLD) { if (counter % 100 == (counter / 100) % 100) { // need to generate garbage in the old gen to provoke mixed GC return replaceInBuffer(index); } else { return BUFFER.get(index); } } else { return replaceInBuffer(index); } } } private Object[] replaceInBuffer(int index) { Object[] objs = new Object[BUF_ARR_LEN]; BUFFER.set(index, objs); return objs; } /** * Returns a random object from the current window within the storage. * A storage element with index from windowStart to windowStart+windowSize. * * @return a random element from the current window within the storage. */ private Object getRandomObject() { int index = (windowStart + RND.nextInt(windowSize)) % N; return STORAGE.get(index); } private static void printAllStackTraces(PrintStream ps) { Map traces = Thread.getAllStackTraces(); for (Thread t : traces.keySet()) { ps.println(t.toString() + " " + t.getState()); for (StackTraceElement traceElement : traces.get(t)) { ps.println("\tat " + traceElement); } } } /** * Thread to create a number of references from BUFFER to STORAGE. */ private static class Worker extends Thread { final TestMultiThreadStressRSet boss; final int refs; // number of refs to OldGen /** * @param boss the tests * @param refsToOldGen how many references to the OldGen to create */ Worker(TestMultiThreadStressRSet boss, int refsToOldGen) { this.boss = boss; this.refs = refsToOldGen; } @Override public void run() { try { while (!boss.isEnough) { Object[] objs = boss.getFromBuffer(); int step = objs.length / refs; for (int i = 0; i < refs; i += step) { objs[i] = boss.getRandomObject(); } boss.counter++; } } catch (Throwable t) { t.printStackTrace(System.out); boss.errorMessage = t.getMessage(); } } } /** * Periodically shifts the current STORAGE window, removing references * in BUFFER that refer to objects outside the window. */ private static class Shifter extends Thread { final TestMultiThreadStressRSet boss; final int sleepTime; final int shift; Shifter(TestMultiThreadStressRSet boss, int sleepTime, int shift) { this.boss = boss; this.sleepTime = sleepTime; this.shift = shift; } @Override public void run() { try { while (!boss.isEnough) { Thread.sleep(sleepTime); boss.windowStart += shift; for (int i = 0; i < boss.OLD; i++) { Object[] objs = boss.BUFFER.get(i); for (int j = 0; j < objs.length; j++) { objs[j] = null; } } if (!WB.g1InConcurrentMark()) { System.out.println("%% start CMC"); WB.g1StartConcMarkCycle(); } else { System.out.println("%% CMC is already in progress"); } } } catch (Throwable t) { t.printStackTrace(System.out); boss.errorMessage = t.getMessage(); } } } }