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      */
createForest(long percent, int heightToSizeRatio, int nodeGarbageSize, GarbageProducer gp, Log _log)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 
treesCount()120     int treesCount() {
121         return trees.length;
122     }
123 
nodesCount()124     long nodesCount() {
125         return allNodesCount;
126     }
127 
128 
129 
130     // Confirms that all trees are balanced and have the correct height.
checkTrees()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 
checkTree(Tree tree)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)
swapSubtrees(Tree t1, Tree t2, int depth, int path)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
swapSubtrees(long count)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;
AtomicCycleInteger(int cycleLength)200         public AtomicCycleInteger(int cycleLength) {
201             super();
202             this.max = cycleLength - 1;
203         }
cycleIncrementAndGet()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
regenerateTrees(long nodesCount)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.
replaceTree(Tree full, Tree partial)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.
replaceTreeWork(TreeNode full, TreeNode partial, boolean dir)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 
printStatistics()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
run()298         public void run() {
299             if (stresser == null) {
300                 stresser = getExecutionController();
301             }
302             while (stresser.continueExecution()) {
303                 doStep();
304             }
305         }
306     }
307 
308     @Override
createRunnable(int i)309     public Runnable createRunnable(int i) {
310         return new Worker();
311     }
312 
getPercentInfoByMBeans()313     public static int getPercentInfoByMBeans() {
314         MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
315         return (int) (100 * mbean.getHeapMemoryUsage().getUsed() / mbean.getHeapMemoryUsage().getMax());
316     }
317 
printMem(long used, long max, String source)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 
init(int longLivePercent)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
run()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 
doStep()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 
main(String[] args)401     public static void main(String[] args) {
402         init(args);
403         GC.runTest(new Concurrent(), args);
404     }
405 
init(String[] args)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
setGarbageProducer(GarbageProducer gp)428     public void setGarbageProducer(GarbageProducer gp) {
429         this.gpOld = gp;
430     }
431 
432 
433     @Override
setGarbageProducer1(GarbageProducer gpYoung)434     public void setGarbageProducer1(GarbageProducer gpYoung) {
435         this.gpYoung = gpYoung;
436     }
437 
438     @Override
setMemoryStrategy(MemoryStrategy ms)439     public void setMemoryStrategy(MemoryStrategy ms) {
440         this.ms = ms;
441     }
442 }
443