1 /*
   2  * Copyright (c) 2008, 2018, 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 
  25 
  26 /*
  27  * @test
  28  * @key stress gc
  29  *
  30  * @summary converted from VM Testbase gc/hashcode/ExternalHashingTest.
  31  * VM Testbase keywords: [gc, stress, stressopt, nonconcurrent, jrockit]
  32  * VM Testbase readme:
  33  * DESCRIPTION
  34  * Test the possible interaction of external hashing and locking on object
  35  * headers.
  36  * The approach is to nearly simultaneously lock/hash a relatively small group
  37  * of objects. We do this repeatedly (munging), recording all hash values
  38  * collected therein.
  39  * After doing this for a large number of groups, we force a garbage collection,
  40  * which would change the hashCode of an object if it hasn't previously been
  41  * hashed. In our case, we _know_ what the previous hashcode was, so we can
  42  * recalculate all of their hashes and compare with the original value.
  43  * If any of the hashCodes hash changed, we know we have a problem.
  44  *
  45  * COMMENTS
  46  * This test was ported from JRockit test suite.
  47  *
  48  * @library /vmTestbase
  49  *          /test/lib
  50  * @run driver jdk.test.lib.FileInstaller . .
  51  * @run main/othervm -XX:-UseGCOverheadLimit gc.hashcode.ExternalHashingTest.ExternalHashingTest
  52  */
  53 
  54 package gc.hashcode.ExternalHashingTest;
  55 
  56 import java.text.SimpleDateFormat;
  57 import java.util.Date;
  58 import java.util.Random;
  59 import java.util.Vector;
  60 
  61 import jdk.test.lib.Utils;
  62 
  63 /**
  64  * Test the possible interaction of external hashing and locking on object
  65  * headers.
  66  *
  67  * The approach is to nearly simultaneously lock/hash a relatively small group
  68  * of objects. We do this repeatedly (munging), recording all hash values
  69  * collected therein.
  70  *
  71  * After doing this for a large number of groups, we force a garbage collection,
  72  * which would change the hashCode of an object if it hasn't previously been
  73  * hashed. In our case, we _know_ what the previous hashcode was, so we can
  74  * recalculate all of their hashes and compare with the original value.
  75  *
  76  * If any of the hashCodes hash changed, we know we have a problem.
  77  */
  78 
  79 public final class ExternalHashingTest {
  80 
  81     /** Random number generator. */
  82     static Random rand = Utils.getRandomInstance();
  83 
  84     /** Goes to true when the threads should start working. */
  85     public static volatile boolean startingGun;
  86 
  87     /** Goes to true when the hashing thread is done. */
  88     public static volatile boolean finishHashing;
  89 
  90     /** Goes to true when the locking thread is done. */
  91     public static volatile boolean finishLocking;
  92 
  93     /** The number of objects in each batch. */
  94     private static final int BATCH_SIZE = 20;
  95 
  96     /** This is the global list of objects that have been hashed. */
  97     static Vector allObjects = new Vector();
  98 
  99     /** This is the corresponding list of hashCodes collected. */
 100     static Vector allHashes = new Vector();
 101 
 102     /** The default milliseconds to run the program. */
 103     private static final long DEFAULT_DURATION = 10000;
 104 
 105     /** All static */
 106     private ExternalHashingTest() {}
 107 
 108     /**
 109      * This object holds garbage. It is a (probably unnecessary){ embellishment
 110      * to increase the amount of garbage created by this benchmark.
 111      * <p>
 112      * It is global to discourage optimizer from removing it.
 113      */
 114     public static Object[] garbageMonger;
 115 
 116     /**
 117      * We need a fairly short pause, since we're not using a semaphore to
 118      * synchronize threads.
 119      */
 120     public static void pause() {
 121         try {
 122             // Thread.sleep(100);
 123             Thread.yield();
 124         } catch (Exception e) {
 125             e.printStackTrace();
 126             System.exit(1);
 127         }
 128     }
 129 
 130     /**
 131      * Returns System.currentTimeMillis() in the Date format.
 132      * @return String
 133      */
 134     private static String getDateString()   {
 135         SimpleDateFormat df = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss z");
 136         Date date = new Date();
 137         date.setTime(System.currentTimeMillis());
 138         return df.format(date);
 139     }
 140 
 141     /**
 142      * Main driver.
 143      * @param args command line arguments aren't used.
 144      */
 145     public static void main(String[] args) {
 146 
 147         long timeToRun = DEFAULT_DURATION;;
 148 
 149         try {
 150             for (int i = 0; i < args.length; i++) {
 151                 if ("-stressTime".equals(args[i])) {
 152                     if (i + 1 == args.length) {
 153                         throw new RuntimeException("Test bug: value of -stressTime option absents");
 154                     }
 155                     timeToRun = Long.parseLong(args[i + 1]);
 156                     if (timeToRun <= 0) {
 157                         throw new RuntimeException("Test bug: value of -stressTime option is not a positive number");
 158                     }
 159                     break;
 160                 }
 161             }
 162         } catch (NumberFormatException e) {
 163             throw new RuntimeException("Test bug: Exception occured while parsing -stressTime option's value", e);
 164         }
 165 
 166         long startTime = System.currentTimeMillis();
 167 
 168         System.out.println("[" + getDateString() + "] Test duration is: " + timeToRun + " ms");
 169         System.out.println("[" + getDateString() + "] Do munge objects...");
 170         while ((System.currentTimeMillis() - startTime) < timeToRun) {
 171             for (int i = 0; i < 100; i++) {
 172                 mungeObjects();
 173             }
 174             System.out.println("[" + getDateString() + "] The next 100 objects are munged...");
 175         }
 176 
 177         // Force a GC (so that objects move their position)
 178         System.out.println("[" + getDateString() + "] Force a GC...");
 179         garbageMonger = null;
 180         System.gc();
 181 
 182         // Now, to check to see if hashes are correct
 183         System.out.println("[" + getDateString() + "] Check hash codes...");
 184         for (int i = 0; i < allObjects.size(); i++) {
 185             Object o = allObjects.elementAt(i);
 186             int hash = ((Integer) allHashes.elementAt(i)).intValue();
 187 
 188             if (o.hashCode() != hash) {
 189                 System.out.println("Inconsistent hash code found (Object "
 190                          + i + " out of " + allObjects.size());
 191                 System.out.println("Object = " + o.toString() + "; hashCode = 0x"
 192                          + Integer.toHexString(o.hashCode())
 193                          + "; expected = 0x" + Integer.toHexString(hash));
 194                 System.exit(1);
 195             }
 196         }
 197 
 198         System.exit(95 /* PASSED */);
 199     }
 200 
 201     /**
 202      * Add a single batch of objects to the mix.
 203      * <p>
 204      * It prepares a list of candidate objects, and presents them to a
 205      * LockerThread and a HasherThread in randomized orders.
 206      * <p>
 207      * The two threads are launched, and control is returned to the caller after
 208      * they have finished their processing.
 209      */
 210     private static void mungeObjects() {
 211 
 212         startingGun = false;
 213         finishHashing = false;
 214         finishLocking = false;
 215 
 216         /* Create the list of victims. */
 217         Object[] candidates = new Object[BATCH_SIZE];
 218         for (int i = 0; i < candidates.length; i++) {
 219             candidates[i] = new Object();
 220         }
 221 
 222         Object[] lockedList = randomize(candidates);
 223         Object[] hashedList = randomize(candidates);
 224         int[] foundHashes = new int[BATCH_SIZE];
 225 
 226         // Launch the child threads
 227         LockerThread locker = new LockerThread(lockedList);
 228         Thread lockerThread = new Thread(locker);
 229         Thread hasherThread = new Thread(new HasherThread(hashedList,
 230                 foundHashes));
 231         lockerThread.start();
 232         hasherThread.start();
 233         startingGun = true;
 234 
 235         while (!finishLocking || !finishHashing) {
 236             pause();
 237         }
 238 
 239         garbageMonger = new Object[BATCH_SIZE];
 240         for (int i = 0; i < BATCH_SIZE; i++) {
 241             /* Add all of the results of this pass to the global list. */
 242             allObjects.add(hashedList[i]);
 243             allHashes.add(new Integer(foundHashes[i]));
 244 
 245             /* Create even more garbage for the GC to find */
 246             garbageMonger[i] = new Object();
 247         }
 248 
 249         // just some noise to make sure that do-nothing code is not optimized
 250         // away.
 251         if (locker.getCount() != BATCH_SIZE) {
 252             throw new InternalError("should not get here");
 253         }
 254     }
 255 
 256     /**
 257      * Return the list of objects in random order
 258      */
 259     private static Object[] randomize(Object[] list) {
 260 
 261         Vector v = new Vector();
 262         for (int i = 0; i < list.length; i++) {
 263             v.add(list[i]);
 264         }
 265 
 266         Object[] result = new Object[list.length];
 267         for (int i = 0; i < list.length; i++) {
 268             int pos = rand.nextInt(list.length - i);
 269             result[i] = v.remove(pos);
 270         }
 271         return result;
 272     }
 273 }
 274 
 275 /**
 276  * This helper thread locks all objects in a list in a given order before
 277  * returning.
 278  */
 279 
 280 class LockerThread implements Runnable {
 281 
 282     /** The list of objects to be locked. */
 283     Object[] theList;
 284 
 285     /**
 286      * This junk counter is an attempt to cause the contents of the synchronized
 287      * block not to be completely optimized away.
 288      */
 289     int count;
 290 
 291     /**
 292      * Construct a LockerThread and provide a list of objects to work with.
 293      * @param list the objects to lock.
 294      */
 295     LockerThread(Object[] list) {
 296         theList = list;
 297         count = 0;
 298     }
 299 
 300     /**
 301      * Proceed to lock the objects...
 302      */
 303     public void run() {
 304         // Synchronize. Wait for caller to say all is go.
 305         while (!ExternalHashingTest.startingGun) {
 306             ExternalHashingTest.pause();
 307         }
 308 
 309         // Perform the locking
 310         for (int i = 0; i < theList.length; i++) {
 311             synchronized (theList[i]) {
 312                 count++;
 313             }
 314         }
 315 
 316         // Tell the caller we are done.
 317         ExternalHashingTest.finishLocking = true;
 318     }
 319 
 320     /**
 321      * Discourage compiler from removing do-nothing count field.
 322      * @return the number of objects locked.
 323      */
 324     public int getCount() {
 325         return count;
 326     }
 327 }
 328 
 329 /**
 330  * This helper thread hashes all objects in a list in a given order before
 331  * returning.
 332  */
 333 
 334 class HasherThread implements Runnable {
 335 
 336     /** The list of objects to be hashed. */
 337     Object[] theList;
 338 
 339     /** The list of hash codes. */
 340     int[] theHashes;
 341 
 342     /**
 343      * Construct a HasherThread and provide a list of objects to work with.
 344      * @param list the objects to hash.
 345      * @param hashes for storing the hash values.
 346      */
 347     HasherThread(Object[] list, int[] hashes) {
 348         theList = list;
 349         theHashes = hashes;
 350     }
 351 
 352     /**
 353      * Proceed to hash the objects.
 354      */
 355     public void run() {
 356         // Synchronize. Wait for caller to say all is go.
 357         while (!ExternalHashingTest.startingGun) {
 358             ExternalHashingTest.pause();
 359         }
 360 
 361         // Perform the hashing (collect for the caller)
 362         for (int i = 0; i < theList.length; i++) {
 363             theHashes[i] = theList[i].hashCode();
 364         }
 365         // Tell the caller we are done.
 366         ExternalHashingTest.finishHashing = true;
 367     }
 368 }