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