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 /testlibrary
  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  */
  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 
 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