1 /*
   2  * Copyright (c) 2003, 2015, 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  * @test
  26  * @bug     4530538
  27  * @summary Basic unit test of memory management testing:
  28  *          1) setUsageThreshold() and getUsageThreshold()
  29  *          2) test low memory detection on the old generation.
  30  *
  31  * @author  Mandy Chung
  32  *
  33  * @library /lib/testlibrary/
  34  * @modules java.management
  35  * @build jdk.testlibrary.* LowMemoryTest MemoryUtil RunUtil
  36   * @run main/timeout=600 LowMemoryTest
  37  * @requires vm.opt.ExplicitGCInvokesConcurrent != "true"
  38  * @requires vm.opt.ExplicitGCInvokesConcurrentAndUnloadsClasses != "true"
  39  * @requires vm.opt.DisableExplicitGC != "true"
  40  */
  41 
  42 import java.lang.management.*;
  43 import java.util.*;
  44 import java.util.concurrent.Phaser;
  45 import javax.management.*;
  46 import javax.management.openmbean.CompositeData;
  47 
  48 public class LowMemoryTest {
  49     private static final MemoryMXBean mm = ManagementFactory.getMemoryMXBean();
  50     private static final List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
  51     private static final Phaser phaser = new Phaser(2);
  52     private static MemoryPoolMXBean mpool = null;
  53     private static boolean trace = false;
  54     private static boolean testFailed = false;
  55     private static final int NUM_TRIGGERS = 5;
  56     private static final int NUM_CHUNKS = 2;
  57     private static final int YOUNG_GEN_SIZE = 8 * 1024 * 1024;
  58     private static long chunkSize;
  59 
  60     /**
  61      * Run the test multiple times with different GC versions.
  62      * First with default command line specified by the framework.
  63      * Then with GC versions specified by the test.
  64      */
  65     public static void main(String a[]) throws Throwable {
  66         final String main = "LowMemoryTest$TestMain";
  67         // Use a low young gen size to ensure that the
  68         // allocated objects are put in the old gen.
  69         final String nmFlag = "-Xmn" + YOUNG_GEN_SIZE;
  70         // Using large pages will change the young gen size,
  71         // make sure we don't use them for this test.
  72         final String lpFlag = "-XX:-UseLargePages";
  73         // Prevent G1 from selecting a large heap region size,
  74         // since that would change the young gen size.
  75         final String g1Flag = "-XX:G1HeapRegionSize=1m";
  76         RunUtil.runTestClearGcOpts(main, nmFlag, lpFlag, "-XX:+UseSerialGC");
  77         RunUtil.runTestClearGcOpts(main, nmFlag, lpFlag, "-XX:+UseParallelGC");
  78         RunUtil.runTestClearGcOpts(main, nmFlag, lpFlag, "-XX:+UseG1GC", g1Flag);
  79         RunUtil.runTestClearGcOpts(main, nmFlag, lpFlag, "-XX:+UseConcMarkSweepGC");
  80     }
  81 
  82     private static volatile boolean listenerInvoked = false;
  83     static class SensorListener implements NotificationListener {
  84         @Override
  85         public void handleNotification(Notification notif, Object handback) {
  86             String type = notif.getType();
  87             if (type.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED) ||
  88                 type.equals(MemoryNotificationInfo.
  89                     MEMORY_COLLECTION_THRESHOLD_EXCEEDED)) {
  90 
  91                 MemoryNotificationInfo minfo = MemoryNotificationInfo.
  92                     from((CompositeData) notif.getUserData());
  93 
  94                 MemoryUtil.printMemoryNotificationInfo(minfo, type);
  95                 listenerInvoked = true;
  96             }
  97         }
  98     }
  99 
 100     static class TestListener implements NotificationListener {
 101         private boolean isRelaxed = false;
 102         private int triggers = 0;
 103         private final long[] count = new long[NUM_TRIGGERS * 2];
 104         private final long[] usedMemory = new long[NUM_TRIGGERS * 2];
 105 
 106         public TestListener() {
 107             isRelaxed = ManagementFactory.getRuntimeMXBean().getInputArguments().contains("-XX:+UseConcMarkSweepGC");
 108         }
 109 
 110         @Override
 111         public void handleNotification(Notification notif, Object handback) {
 112             MemoryNotificationInfo minfo = MemoryNotificationInfo.
 113                 from((CompositeData) notif.getUserData());
 114             count[triggers] = minfo.getCount();
 115             usedMemory[triggers] = minfo.getUsage().getUsed();
 116             triggers++;
 117         }
 118         public void checkResult() throws Exception {
 119             if (!checkValue(triggers, NUM_TRIGGERS)) {
 120                 throw new RuntimeException("Unexpected number of triggers = " +
 121                     triggers + " but expected to be " + NUM_TRIGGERS);
 122             }
 123 
 124             for (int i = 0; i < triggers; i++) {
 125                 if (!checkValue(count[i], i + 1)) {
 126                     throw new RuntimeException("Unexpected count of" +
 127                         " notification #" + i +
 128                         " count = " + count[i] +
 129                         " but expected to be " + (i+1));
 130                 }
 131                 if (usedMemory[i] < newThreshold) {
 132                     throw new RuntimeException("Used memory = " +
 133                         usedMemory[i] + " is less than the threshold = " +
 134                         newThreshold);
 135                 }
 136             }
 137         }
 138 
 139         private boolean checkValue(int value, int target) {
 140             return checkValue((long)value, target);
 141         }
 142 
 143         private boolean checkValue(long value, int target) {
 144             if (!isRelaxed) {
 145                 return value == target;
 146             } else {
 147                 return value >= target;
 148             }
 149         }
 150     }
 151 
 152     private static long newThreshold;
 153 
 154     private static class TestMain {
 155         public static void main(String args[]) throws Exception {
 156             if (args.length > 0 && args[0].equals("trace")) {
 157                 trace = true;
 158             }
 159 
 160             // Find the Old generation which supports low memory detection
 161             ListIterator iter = pools.listIterator();
 162             while (iter.hasNext()) {
 163                 MemoryPoolMXBean p = (MemoryPoolMXBean) iter.next();
 164                 if (p.getType() == MemoryType.HEAP &&
 165                     p.isUsageThresholdSupported()) {
 166                     mpool = p;
 167                     if (trace) {
 168                         System.out.println("Selected memory pool for low memory " +
 169                             "detection.");
 170                         MemoryUtil.printMemoryPool(mpool);
 171                     }
 172                     break;
 173                 }
 174             }
 175 
 176             TestListener listener = new TestListener();
 177             SensorListener l2 = new SensorListener();
 178             NotificationEmitter emitter = (NotificationEmitter) mm;
 179             emitter.addNotificationListener(listener, null, null);
 180             emitter.addNotificationListener(l2, null, null);
 181 
 182             Thread allocator = new AllocatorThread();
 183             Thread sweeper = new SweeperThread();
 184 
 185             // The chunk size needs to be larger than YOUNG_GEN_SIZE,
 186             // otherwise we will get intermittent failures when objects
 187             // end up in the young gen instead of the old gen.
 188             final long epsilon = 1024;
 189             chunkSize = YOUNG_GEN_SIZE + epsilon;
 190 
 191             MemoryUsage mu = mpool.getUsage();
 192             newThreshold = mu.getUsed() + (chunkSize * NUM_CHUNKS);
 193 
 194             // Sanity check. Make sure the new threshold isn't too large.
 195             // Tweak the test if this fails.
 196             final long headRoom = chunkSize * 2;
 197             final long max = mu.getMax();
 198             if (max != -1 && newThreshold > max - headRoom) {
 199                 throw new RuntimeException("TEST FAILED: newThreshold: " + newThreshold +
 200                         " is too near the maximum old gen size: " + max +
 201                         " used: " + mu.getUsed() + " headRoom: " + headRoom);
 202             }
 203 
 204             System.out.println("Setting threshold for " + mpool.getName() +
 205                 " from " + mpool.getUsageThreshold() + " to " + newThreshold +
 206                 ".  Current used = " + mu.getUsed());
 207             mpool.setUsageThreshold(newThreshold);
 208 
 209             if (mpool.getUsageThreshold() != newThreshold) {
 210                 throw new RuntimeException("TEST FAILED: " +
 211                 "Threshold for Memory pool " + mpool.getName() +
 212                 "is " + mpool.getUsageThreshold() + " but expected to be" +
 213                 newThreshold);
 214             }
 215 
 216 
 217             allocator.start();
 218             // Force Allocator start first
 219             phaser.arriveAndAwaitAdvance();
 220             sweeper.start();
 221 
 222 
 223             try {
 224                 allocator.join();
 225                 // Wait until AllocatorThread's done
 226                 phaser.arriveAndAwaitAdvance();
 227                 sweeper.join();
 228             } catch (InterruptedException e) {
 229                 System.out.println("Unexpected exception:" + e);
 230                 testFailed = true;
 231             }
 232 
 233             listener.checkResult();
 234 
 235             if (testFailed)
 236                 throw new RuntimeException("TEST FAILED.");
 237 
 238             System.out.println(RunUtil.successMessage);
 239 
 240         }
 241     }
 242 
 243     private static void goSleep(long ms) {
 244         try {
 245             Thread.sleep(ms);
 246         } catch (InterruptedException e) {
 247             System.out.println("Unexpected exception:" + e);
 248             testFailed = true;
 249         }
 250     }
 251 
 252     private static final List<Object> objectPool = new ArrayList<>();
 253     static class AllocatorThread extends Thread {
 254         public void doTask() {
 255             int iterations = 0;
 256             int numElements = (int) (chunkSize / 4); // minimal object size
 257             while (!listenerInvoked || mpool.getUsage().getUsed() < mpool.getUsageThreshold()) {
 258                 iterations++;
 259                 if (trace) {
 260                     System.out.println("   Iteration " + iterations +
 261                         ": before allocation " +
 262                         mpool.getUsage().getUsed());
 263                 }
 264 
 265                 Object[] o = new Object[numElements];
 266                 if (iterations <= NUM_CHUNKS) {
 267                     // only hold a reference to the first NUM_CHUNKS
 268                     // allocated objects
 269                     objectPool.add(o);
 270                 }
 271 
 272                 if (trace) {
 273                     System.out.println("               " +
 274                         "  after allocation " +
 275                         mpool.getUsage().getUsed());
 276                 }
 277                 goSleep(100);
 278             }
 279         }
 280         @Override
 281         public void run() {
 282             for (int i = 1; i <= NUM_TRIGGERS; i++) {
 283                 // Sync with SweeperThread's second phase.
 284                 phaser.arriveAndAwaitAdvance();
 285                 System.out.println("AllocatorThread is doing task " + i +
 286                     " phase " + phaser.getPhase());
 287                 doTask();
 288                 // Sync with SweeperThread's first phase.
 289                 phaser.arriveAndAwaitAdvance();
 290                 System.out.println("AllocatorThread done task " + i +
 291                     " phase " + phaser.getPhase());
 292                 if (testFailed) {
 293                     return;
 294                 }
 295             }
 296         }
 297     }
 298 
 299     static class SweeperThread extends Thread {
 300         private void doTask() {
 301             for (; mpool.getUsage().getUsed() >=
 302                        mpool.getUsageThreshold();) {
 303                 // clear all allocated objects and invoke GC
 304                 objectPool.clear();
 305                 mm.gc();
 306                 goSleep(100);
 307             }
 308         }
 309         @Override
 310         public void run() {
 311             for (int i = 1; i <= NUM_TRIGGERS; i++) {
 312                 // Sync with AllocatorThread's first phase.
 313                 phaser.arriveAndAwaitAdvance();
 314                 System.out.println("SweepThread is doing task " + i +
 315                     " phase " + phaser.getPhase());
 316                 doTask();
 317 
 318                 listenerInvoked = false;
 319 
 320                 // Sync with AllocatorThread's second phase.
 321                 phaser.arriveAndAwaitAdvance();
 322                 System.out.println("SweepThread done task " + i +
 323                     " phase " + phaser.getPhase());
 324                 if (testFailed) return;
 325             }
 326         }
 327     }
 328 }