1 /*
   2  * Copyright (c) 2010, 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 package vm.gc.concurrent;
  24 
  25 import java.lang.management.ManagementFactory;
  26 import java.lang.management.MemoryMXBean;
  27 import java.lang.management.MemoryUsage;
  28 import java.util.Random;
  29 import java.util.concurrent.atomic.AtomicInteger;
  30 import java.util.concurrent.locks.Lock;
  31 import java.util.concurrent.locks.ReentrantLock;
  32 import nsk.share.TestFailure;
  33 import nsk.share.gc.GC;
  34 import nsk.share.gc.Memory;
  35 import nsk.share.gc.ThreadedGCTest;
  36 import nsk.share.gc.gp.GarbageProducer;
  37 import nsk.share.gc.gp.GarbageProducer1Aware;
  38 import nsk.share.gc.gp.GarbageProducerAware;
  39 import nsk.share.gc.gp.MemoryStrategy;
  40 import nsk.share.gc.gp.MemoryStrategyAware;
  41 import nsk.share.gc.tree.*;
  42 import nsk.share.log.Log;
  43 import nsk.share.test.ExecutionController;
  44 
  45 
  46 class Forest {
  47 
  48     // the actual size of TreeNode in bytes in the memory calculated as occupied memory / count of nodes
  49     static int nodeSize;
  50 
  51     static long treeSize;
  52 
  53     private static long allNodesCount;
  54 
  55     /* log from test */
  56     static Log log;
  57 
  58 
  59     static int treeHeight;
  60 
  61     static long actuallyMut = 0;
  62     private static Forest instance = new Forest();
  63     private Tree[] trees;
  64     private Lock[] locks;
  65     private static Random rnd = new Random();
  66 
  67     private int nodeGarbageSize;
  68 
  69     private GarbageProducer gp;
  70     /*
  71      * Create array of trees occupyng given percent of heap
  72      */
  73     static Forest createForest(long percent, int heightToSizeRatio, int nodeGarbageSize, GarbageProducer gp, Log _log) {
  74         log = _log;
  75 
  76         long size = Runtime.getRuntime().maxMemory() * percent / 100;
  77         treeHeight = Memory.balancedTreeHeightFromMemory(size, (int) new TreeNode(nodeGarbageSize).getTotalSize());
  78         int ntrees = 0;
  79         while (treeHeight * heightToSizeRatio > ntrees) {
  80             ntrees++;
  81             treeHeight = Memory.balancedTreeHeightFromMemory(size / ntrees, (int) new TreeNode(nodeGarbageSize).getTotalSize());
  82         }
  83 
  84         log.debug("The expected forest paramteres: tree height = " + treeHeight  + " number of trees = " + ntrees
  85                 + " size = " +  new TreeNode(nodeGarbageSize).getTotalSize());
  86         Tree[] localTrees = new Tree[ntrees * 4];
  87         Lock[] localLocks = new Lock[ntrees * 4];
  88         for (int i = 0; i < ntrees * 4; i++) {
  89             localTrees[i] = new Tree(Memory.makeBalancedTreeNode(treeHeight, nodeGarbageSize, gp));
  90             localLocks[i] = new ReentrantLock();
  91 
  92             int numOfAttempts = 0;
  93             if (Concurrent.getPercentInfoByMBeans() > percent) {
  94                 log.debug("Attempt to System.gc() before control check. (" + numOfAttempts++ + ")");
  95                 System.gc();
  96                 if (Concurrent.getPercentInfoByMBeans() > percent) {
  97                     instance.trees = new Tree[i];
  98                     instance.locks = new Lock[i];
  99                     for (int j = 0; j < i; j++) {
 100                         instance.trees[j] = localTrees[j];
 101                         instance.locks[j] = localLocks[j];
 102                     }
 103                     allNodesCount = Memory.balancedTreeNodes(treeHeight) * instance.trees.length;
 104                     nodeSize = (int) (ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() / allNodesCount);
 105                     treeSize = Memory.balancedTreeSize(treeHeight, nodeSize);
 106                     instance.where = new AtomicCycleInteger(instance.trees.length);
 107                     instance.nodeGarbageSize = nodeGarbageSize;
 108 
 109                     log.debug("The forest real paramteres: tree height = " + treeHeight  + " number of trees = " + instance.trees.length
 110                             + " number of nodes = " + allNodesCount);
 111                     log.debug("Approximate node size = " + nodeSize + " calc = " + instance.trees[0].getRoot().getSize());
 112                     return instance;
 113                 }
 114             }
 115         }
 116         throw new TestFailure("Should not reach here. The correct exit point is inside cycle");
 117     }
 118 
 119 
 120     int treesCount() {
 121         return trees.length;
 122     }
 123 
 124     long nodesCount() {
 125         return allNodesCount;
 126     }
 127 
 128 
 129 
 130     // Confirms that all trees are balanced and have the correct height.
 131     void checkTrees() {
 132         for (int i = 0; i < trees.length; i++) {
 133             locks[i].lock();
 134             checkTree(trees[i]);
 135             locks[i].unlock();
 136         }
 137     }
 138 
 139     private static void checkTree(Tree tree) {
 140         TreeNode root = tree.getRoot();
 141         int h1 = root.getHeight();
 142         int h2 = root.getShortestPath();
 143         if ((h1 != treeHeight) || (h2 != treeHeight)) {
 144             throw new TestFailure("The tree is not balanced expected " + treeHeight
 145                     + " value = " + h1 + " shortedtPath = " + h2);
 146         }
 147     }
 148 
 149     // Swap subtrees in 2 trees, the the path is used
 150     // as sequence of 1-0 to select subtree (left-reight sequence)
 151     static void swapSubtrees(Tree t1, Tree t2, int depth, int path) {
 152         TreeNode tn1 = t1.getRoot();
 153         TreeNode tn2 = t2.getRoot();
 154         for (int i = 0; i < depth; i++) {
 155             if ((path & 1) == 0) {
 156                 tn1 = tn1.getLeft();
 157                 tn2 = tn2.getLeft();
 158             } else {
 159                 tn1 = tn1.getRight();
 160                 tn2 = tn2.getRight();
 161             }
 162             path >>= 1;
 163         }
 164         TreeNode tmp;
 165         if ((path & 1) == 0) {
 166             tmp = tn1.getLeft();
 167             tn1.setLeft(tn2.getLeft());
 168             tn2.setLeft(tmp);
 169         } else {
 170             tmp = tn1.getRight();
 171             tn1.setRight(tn2.getRight());
 172             tn2.setLeft(tmp);
 173         }
 174     }
 175 
 176 
 177     // Interchanges two randomly selected subtrees (of same size and depth) several times
 178     void swapSubtrees(long count) {
 179         for (int i = 0; i < count; i++) {
 180             int index1 = rnd.nextInt(trees.length);
 181             int index2 = rnd.nextInt(trees.length);
 182             int depth = rnd.nextInt(treeHeight);
 183             int path = rnd.nextInt();
 184             locks[index1].lock();
 185             // Skip the round to avoid deadlocks
 186             if (locks[index2].tryLock()) {
 187                 swapSubtrees(trees[index1], trees[index2], depth, path);
 188                 actuallyMut += 2;
 189                 locks[index2].unlock();
 190             }
 191             locks[index1].unlock();
 192 
 193         }
 194 
 195     }
 196 
 197 
 198     static class AtomicCycleInteger extends AtomicInteger {
 199         private int max;
 200         public AtomicCycleInteger(int cycleLength) {
 201             super();
 202             this.max = cycleLength - 1;
 203         }
 204         public int cycleIncrementAndGet() {
 205             for (;;) {
 206                 int current = get();
 207                 int next = (current == max ? 0 : current + 1);
 208                 if (compareAndSet(current, next)) {
 209                     return next;
 210                 }
 211             }
 212         }
 213     }
 214 
 215     // the index in tree array which should be chnaged during next regeneration
 216     AtomicCycleInteger where = null;
 217 
 218     // generate new full and partial trees in our forest
 219     void regenerateTrees(long nodesCount) {
 220         int full = (int) (nodesCount / Memory.balancedTreeNodes(treeHeight)) ;
 221         int partial = (int) nodesCount % (Memory.balancedTreeNodes(treeHeight));
 222         for (int i = 0; i < full; i++) {
 223             int idx = where.cycleIncrementAndGet();
 224             locks[idx].lock();
 225             trees[idx] = new Tree(Memory.makeBalancedTreeNode(treeHeight, nodeGarbageSize));
 226             locks[idx].unlock();
 227         }
 228         while (partial > 0) {
 229             int h = Memory.balancedTreeHeightFromNodes(partial);
 230             Tree newTree = new Tree(Memory.makeBalancedTreeNode(h, nodeGarbageSize));
 231             int idx = where.cycleIncrementAndGet();
 232             locks[idx].lock();
 233             replaceTree(trees[idx], newTree);
 234             locks[idx].unlock();
 235             partial = partial - Memory.balancedTreeNodes(h);
 236         }
 237     }
 238 
 239 
 240     // Given a balanced tree full and a smaller balanced tree partial,
 241     // replaces an appropriate subtree of full by partial, taking care
 242     // to preserve the shape of the full tree.
 243     private static void replaceTree(Tree full, Tree partial) {
 244         boolean dir = (partial.getHeight() % 2) == 0;
 245         actuallyMut++;
 246         replaceTreeWork(full.getRoot(), partial.getRoot(), dir);
 247     }
 248 
 249     // Called only by replaceTree (below) and by itself.
 250     static void replaceTreeWork(TreeNode full, TreeNode partial,
 251             boolean dir) {
 252         boolean canGoLeft = full.getLeft() != null && full.getLeft().getHeight() > partial.getHeight();
 253         boolean canGoRight = full.getRight() != null && full.getRight().getHeight() > partial.getHeight();
 254         if (canGoLeft && canGoRight) {
 255             if (dir) {
 256                 replaceTreeWork(full.getLeft(), partial, !dir);
 257             } else {
 258                 replaceTreeWork(full.getRight(), partial, !dir);
 259             }
 260         } else if (!canGoLeft && !canGoRight) {
 261             if (dir) {
 262                 full.setLeft(partial);
 263             } else {
 264                 full.setRight(partial);
 265             }
 266         } else if (!canGoLeft) {
 267             full.setLeft(partial);
 268         } else {
 269             full.setRight(partial);
 270         }
 271     }
 272 
 273 
 274 
 275 }
 276 public class Concurrent extends ThreadedGCTest implements GarbageProducerAware, GarbageProducer1Aware, MemoryStrategyAware {
 277 
 278     // Heap as tree
 279     Forest forest;
 280 
 281     // GP for old gargbage production
 282     GarbageProducer gpOld;
 283 
 284     // GP for young gargbage production
 285     GarbageProducer gpYoung;
 286 
 287     MemoryStrategy ms;
 288 
 289     private void printStatistics() {
 290         log.debug("Actual mutations = " + forest.actuallyMut);
 291     }
 292 
 293     private class Worker implements Runnable {
 294 
 295         private ExecutionController stresser;
 296 
 297         @Override
 298         public void run() {
 299             if (stresser == null) {
 300                 stresser = getExecutionController();
 301             }
 302             while (stresser.continueExecution()) {
 303                 doStep();
 304             }
 305         }
 306     }
 307 
 308     @Override
 309     public Runnable createRunnable(int i) {
 310         return new Worker();
 311     }
 312 
 313     public static int getPercentInfoByMBeans() {
 314         MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
 315         return (int) (100 * mbean.getHeapMemoryUsage().getUsed() / mbean.getHeapMemoryUsage().getMax());
 316     }
 317 
 318     private void printMem(long used, long max, String source) {
 319         log.debug("The Memory after allocation (" + source + "): ");
 320         log.debug("Used = " + used + " Max = " + max + " Percent = " + (100 * used / max));
 321     }
 322 
 323     // Command-line parameters.
 324     // young garbage in percent and absolute
 325     private static int youngPercent = 0;
 326     long youngGarbageSize;
 327     // mutation rate (parcent and absolute trees)
 328     private static int ptrMutRate = 50;
 329     long mutateTrees;
 330     // percent of heap to occupy by forest (long live garbage)
 331     private static int livePercent = 60;
 332     // the minimum of which should be available for forest
 333     // test fails if it is not possible to use 60% of heap
 334     private static final int MIN_AVAILABLE_MEM = 60;
 335     // percent of forest to reallocate each step
 336     private static int reallocatePercent = 30;
 337     long reallocateSizeInNodes;
 338     // sleep time in ms
 339     private static int sleepTime = 100;
 340 
 341     private void init(int longLivePercent) {
 342         int numberOfThreads = runParams.getNumberOfThreads();
 343         forest = Forest.createForest(longLivePercent, numberOfThreads,
 344                 (int) Math.sqrt(ms.getSize(Runtime.getRuntime().maxMemory())), gpOld, log);
 345 
 346         youngGarbageSize = Runtime.getRuntime().maxMemory() * youngPercent / 100 / numberOfThreads;
 347         reallocateSizeInNodes = forest.nodesCount() * reallocatePercent / 100 / numberOfThreads;
 348         mutateTrees = forest.treesCount() * ptrMutRate / 100 / numberOfThreads / 2;
 349 
 350         log.debug("Young Gen = " + youngGarbageSize);
 351         log.debug("Forest contains " + forest.treesCount() + " trees and " + forest.nodesCount() + " nodes.");
 352         log.debug("Count of nodes to reallocate = " + reallocateSizeInNodes);
 353         log.debug("Count of tree pairs to exchange nodes = " + mutateTrees);
 354         log.debug("Sleep time = " + sleepTime);
 355 
 356         // print some info
 357         MemoryUsage mbean = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
 358         printMem(mbean.getUsed(), mbean.getMax(), "Beans");
 359         printMem(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().freeMemory(),
 360                 Runtime.getRuntime().maxMemory(), "System");
 361     }
 362 
 363     @Override
 364     public void run() {
 365         try {
 366             init(livePercent);
 367         } catch (OutOfMemoryError oome) {
 368             if (livePercent > MIN_AVAILABLE_MEM) {
 369                 log.debug("Unable to use " + livePercent + " use only " + MIN_AVAILABLE_MEM);
 370                 init(MIN_AVAILABLE_MEM);
 371             }
 372         }
 373         super.run();
 374         printStatistics();
 375     }
 376 
 377 
 378 
 379     private void doStep() {
 380         // allocate some young garbage
 381         if (youngGarbageSize != 0) {
 382             gpYoung.create(youngGarbageSize);
 383         }
 384 
 385         // allocate some long-live garbage (attached to our trees)
 386         forest.regenerateTrees(reallocateSizeInNodes);
 387 
 388         // mutate pointers
 389         forest.swapSubtrees(mutateTrees);
 390 
 391         // sleep to give GC time for some concurrent actions
 392         try {
 393             Thread.sleep(sleepTime);
 394         } catch (InterruptedException ie) {
 395         }
 396 
 397         // verify trees, also read all pointers
 398         forest.checkTrees();
 399     }
 400 
 401     public static void main(String[] args) {
 402         init(args);
 403         GC.runTest(new Concurrent(), args);
 404     }
 405 
 406     public static void init(String[] args) {
 407         for (int i = 0; i < args.length; ++i) {
 408             if (args[i].equals("-lp")) {
 409                 // percent of long lived objects
 410                 livePercent = Integer.parseInt(args[++i]);
 411             } else if (args[i].equals("-rp")) {
 412                 // percent of trees to reallocate
 413                 reallocatePercent = Integer.parseInt(args[++i]);
 414             } else if (args[i].equals("-yp")) {
 415                 // percent of young objects
 416                 youngPercent = Integer.parseInt(args[++i]);
 417             } else if (args[i].equals("-mr")) {
 418                 // percent of trees to exchange (mutate)
 419                 ptrMutRate = Integer.parseInt(args[++i]);
 420             } else if (args[i].equals("-st")) {
 421                 // sleep time in ms
 422                 sleepTime = Integer.parseInt(args[++i]);
 423             }
 424         }
 425     }
 426 
 427     @Override
 428     public void setGarbageProducer(GarbageProducer gp) {
 429         this.gpOld = gp;
 430     }
 431 
 432 
 433     @Override
 434     public void setGarbageProducer1(GarbageProducer gpYoung) {
 435         this.gpYoung = gpYoung;
 436     }
 437 
 438     @Override
 439     public void setMemoryStrategy(MemoryStrategy ms) {
 440         this.ms = ms;
 441     }
 442 }