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