1 /*
   2  * Copyright (c) 2003, 2010, 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 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 long chunkSize;
  54 
  55     /**
  56      * Run the test multiple times with different GC versions.
  57      * First with default command line specified by the framework.
  58      * Then with GC versions specified by the test.
  59      */
  60     public static void main(String a[]) throws Throwable {
  61         final String main = "LowMemoryTest$TestMain";
  62         RunUtil.runTestKeepGcOpts(main);
  63         RunUtil.runTestClearGcOpts(main, "-XX:+UseSerialGC");
  64         RunUtil.runTestClearGcOpts(main, "-XX:+UseParallelGC");
  65         RunUtil.runTestClearGcOpts(main, "-XX:+UseG1GC");
  66         RunUtil.runTestClearGcOpts(main, "-XX:+UseConcMarkSweepGC");
  67     }
  68 
  69     private static volatile boolean listenerInvoked = false;
  70     static class SensorListener implements NotificationListener {
  71         @Override
  72         public void handleNotification(Notification notif, Object handback) {
  73             String type = notif.getType();
  74             if (type.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED) ||
  75                 type.equals(MemoryNotificationInfo.
  76                     MEMORY_COLLECTION_THRESHOLD_EXCEEDED)) {
  77 
  78                 MemoryNotificationInfo minfo = MemoryNotificationInfo.
  79                     from((CompositeData) notif.getUserData());
  80 
  81                 MemoryUtil.printMemoryNotificationInfo(minfo, type);
  82                 listenerInvoked = true;
  83             }
  84         }
  85     }
  86 
  87     static class TestListener implements NotificationListener {
  88         private int triggers = 0;
  89         private final long[] count = new long[NUM_TRIGGERS * 2];
  90         private final long[] usedMemory = new long[NUM_TRIGGERS * 2];
  91         @Override
  92         public void handleNotification(Notification notif, Object handback) {
  93             MemoryNotificationInfo minfo = MemoryNotificationInfo.
  94                 from((CompositeData) notif.getUserData());
  95             count[triggers] = minfo.getCount();
  96             usedMemory[triggers] = minfo.getUsage().getUsed();
  97             triggers++;
  98         }
  99         public void checkResult() throws Exception {
 100             if (triggers != NUM_TRIGGERS) {
 101                 throw new RuntimeException("Unexpected number of triggers = " +
 102                     triggers + " but expected to be " + NUM_TRIGGERS);
 103             }
 104 
 105             for (int i = 0; i < triggers; i++) {
 106                 if (count[i] != i+1) {
 107                     throw new RuntimeException("Unexpected count of" +
 108                         " notification #" + i +
 109                         " count = " + count[i] +
 110                         " but expected to be " + (i+1));
 111                 }
 112                 if (usedMemory[i] < newThreshold) {
 113                     throw new RuntimeException("Used memory = " +
 114                         usedMemory[i] + " is less than the threshold = " +
 115                         newThreshold);
 116                 }
 117             }
 118         }
 119     }
 120 
 121     private static long newThreshold;
 122 
 123     private static class TestMain {
 124         public static void main(String args[]) throws Exception {
 125             if (args.length > 0 && args[0].equals("trace")) {
 126                 trace = true;
 127             }
 128 
 129             // Find the Old generation which supports low memory detection
 130             ListIterator iter = pools.listIterator();
 131             while (iter.hasNext()) {
 132                 MemoryPoolMXBean p = (MemoryPoolMXBean) iter.next();
 133                 if (p.getType() == MemoryType.HEAP &&
 134                     p.isUsageThresholdSupported()) {
 135                     mpool = p;
 136                     if (trace) {
 137                         System.out.println("Selected memory pool for low memory " +
 138                             "detection.");
 139                         MemoryUtil.printMemoryPool(mpool);
 140                     }
 141                     break;
 142                 }
 143             }
 144 
 145             TestListener listener = new TestListener();
 146             SensorListener l2 = new SensorListener();
 147             NotificationEmitter emitter = (NotificationEmitter) mm;
 148             emitter.addNotificationListener(listener, null, null);
 149             emitter.addNotificationListener(l2, null, null);
 150 
 151             Thread allocator = new AllocatorThread();
 152             Thread sweeper = new SweeperThread();
 153 
 154             // Now set threshold
 155             MemoryUsage mu = mpool.getUsage();
 156             chunkSize = (mu.getMax() - mu.getUsed()) / 20;
 157             newThreshold = mu.getUsed() + (chunkSize * NUM_CHUNKS);
 158 
 159             System.out.println("Setting threshold for " + mpool.getName() +
 160                 " from " + mpool.getUsageThreshold() + " to " + newThreshold +
 161                 ".  Current used = " + mu.getUsed());
 162             mpool.setUsageThreshold(newThreshold);
 163 
 164             if (mpool.getUsageThreshold() != newThreshold) {
 165                 throw new RuntimeException("TEST FAILED: " +
 166                 "Threshold for Memory pool " + mpool.getName() +
 167                 "is " + mpool.getUsageThreshold() + " but expected to be" +
 168                 newThreshold);
 169             }
 170 
 171 
 172             allocator.start();
 173             // Force Allocator start first
 174             phaser.arriveAndAwaitAdvance();
 175             sweeper.start();
 176 
 177 
 178             try {
 179                 allocator.join();
 180                 // Wait until AllocatorThread's done
 181                 phaser.arriveAndAwaitAdvance();
 182                 sweeper.join();
 183             } catch (InterruptedException e) {
 184                 System.out.println("Unexpected exception:" + e);
 185                 testFailed = true;
 186             }
 187 
 188             listener.checkResult();
 189 
 190             if (testFailed)
 191                 throw new RuntimeException("TEST FAILED.");
 192 
 193             System.out.println(RunUtil.successMessage);
 194 
 195         }
 196     }
 197 
 198     private static void goSleep(long ms) {
 199         try {
 200             Thread.sleep(ms);
 201         } catch (InterruptedException e) {
 202             System.out.println("Unexpected exception:" + e);
 203             testFailed = true;
 204         }
 205     }
 206 
 207     private static final List<Object> objectPool = new ArrayList<>();
 208     static class AllocatorThread extends Thread {
 209         public void doTask() {
 210             int iterations = 0;
 211             int numElements = (int) (chunkSize / 4); // minimal object size
 212             while (!listenerInvoked || mpool.getUsage().getUsed() < mpool.getUsageThreshold()) {
 213                 iterations++;
 214                 if (trace) {
 215                     System.out.println("   Iteration " + iterations +
 216                         ": before allocation " +
 217                         mpool.getUsage().getUsed());
 218                 }
 219 
 220                 Object[] o = new Object[numElements];
 221                 if (iterations <= NUM_CHUNKS) {
 222                     // only hold a reference to the first NUM_CHUNKS
 223                     // allocated objects
 224                     objectPool.add(o);
 225                 }
 226 
 227                 if (trace) {
 228                     System.out.println("               " +
 229                         "  after allocation " +
 230                         mpool.getUsage().getUsed());
 231                 }
 232                 goSleep(100);
 233             }
 234         }
 235         @Override
 236         public void run() {
 237             for (int i = 1; i <= NUM_TRIGGERS; i++) {
 238                 // Sync with SweeperThread's second phase.
 239                 phaser.arriveAndAwaitAdvance();
 240                 System.out.println("AllocatorThread is doing task " + i +
 241                     " phase " + phaser.getPhase());
 242                 doTask();
 243                 // Sync with SweeperThread's first phase.
 244                 phaser.arriveAndAwaitAdvance();
 245                 System.out.println("AllocatorThread done task " + i +
 246                     " phase " + phaser.getPhase());
 247                 if (testFailed) {
 248                     return;
 249                 }
 250             }
 251         }
 252     }
 253 
 254     static class SweeperThread extends Thread {
 255         private void doTask() {
 256             for (; mpool.getUsage().getUsed() >=
 257                        mpool.getUsageThreshold();) {
 258                 // clear all allocated objects and invoke GC
 259                 objectPool.clear();
 260                 mm.gc();
 261                 goSleep(100);
 262             }
 263         }
 264         @Override
 265         public void run() {
 266             for (int i = 1; i <= NUM_TRIGGERS; i++) {
 267                 // Sync with AllocatorThread's first phase.
 268                 phaser.arriveAndAwaitAdvance();
 269                 System.out.println("SweepThread is doing task " + i +
 270                     " phase " + phaser.getPhase());
 271                 doTask();
 272 
 273                 listenerInvoked = false;
 274 
 275                 // Sync with AllocatorThread's second phase.
 276                 phaser.arriveAndAwaitAdvance();
 277                 System.out.println("SweepThread done task " + i +
 278                     " phase " + phaser.getPhase());
 279                 if (testFailed) return;
 280             }
 281         }
 282     }
 283 }