1 /*
   2  * Copyright (c) 2003, 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 
  24 /*
  25  * @test
  26  * @bug     4530538 6980984
  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  * @modules jdk.management
  34  * @build MemoryManagement MemoryUtil
  35  * @run main/othervm/timeout=600 -Xmn8m -XX:+IgnoreUnrecognizedVMOptions -XX:G1HeapRegionSize=1 -XX:-UseLargePages MemoryManagement
  36  */
  37 
  38 import java.lang.management.*;
  39 import java.util.*;
  40 import javax.management.*;
  41 import javax.management.openmbean.CompositeData;
  42 
  43 public class MemoryManagement {
  44     private static final MemoryMXBean mm = ManagementFactory.getMemoryMXBean();
  45     private static final List pools =
  46             Collections.synchronizedList(ManagementFactory.getMemoryPoolMXBeans());
  47     private static final List managers =
  48             Collections.synchronizedList(ManagementFactory.getMemoryManagerMXBeans());
  49     private static volatile MemoryPoolMXBean mpool = null;
  50     private static volatile boolean trace = false;
  51     private static volatile boolean testFailed = false;
  52     private static final int NUM_CHUNKS = 2;
  53     // Must match -Xmn set on the @run line
  54     private static final int YOUNG_GEN_SIZE = 8 * 1024 * 1024;
  55     private static volatile long chunkSize;
  56     private static volatile int listenerInvoked = 0;
  57 
  58     static class SensorListener implements NotificationListener {
  59         public void handleNotification(Notification notif, Object handback) {
  60             String type = notif.getType();
  61             if (type.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED) ||
  62                 type.equals(MemoryNotificationInfo.
  63                     MEMORY_COLLECTION_THRESHOLD_EXCEEDED)) {
  64 
  65                 MemoryNotificationInfo minfo = MemoryNotificationInfo.
  66                     from((CompositeData) notif.getUserData());
  67 
  68                 MemoryUtil.printMemoryNotificationInfo(minfo, type);
  69                 listenerInvoked++;
  70             }
  71         }
  72     }
  73 
  74     private static long newThreshold;
  75     public static void main(String args[]) throws Exception {
  76         if (args.length > 0 && args[0].equals("trace")) {
  77             trace = true;
  78         }
  79 
  80         if (trace) {
  81             MemoryUtil.printMemoryPools(pools);
  82             MemoryUtil.printMemoryManagers(managers);
  83         }
  84 
  85         // Find a pool which which supports low memory detection
  86         ListIterator iter = pools.listIterator();
  87         while (iter.hasNext()) {
  88             MemoryPoolMXBean p = (MemoryPoolMXBean) iter.next();
  89             if (p.getType() == MemoryType.HEAP &&
  90                 p.isUsageThresholdSupported()) {
  91                 if (!p.getName().equals("G1 Old Space")) {
  92                     // In G1, humongous objects are tracked in the old space only in
  93                     // legacy monitoring mode. In default mode, G1 tracks humongous
  94                     // objects in the humongous space, which latter also supports a
  95                     // usage threshold. Since we're allocating humongous objects in
  96                     // this test, in default mode the old space doesn't change. For
  97                     // this test, we use the old space in legacy mode (it's called
  98                     // "G1 Old Gen" and the humongous space in default mode. If we
  99                     // used "G1 Old Space" in default mode, notification would never
 100                     // happen.
 101                     mpool = p;
 102                     if (trace) {
 103                         System.out.println("Selected memory pool for low memory " +
 104                                            "detection.");
 105                         MemoryUtil.printMemoryPool(mpool);
 106                     }
 107                     break;
 108                 }
 109             }
 110         }
 111         if (mpool == null) {
 112             throw new RuntimeException("TEST FAILED: No heap pool found");
 113         }
 114 
 115         SensorListener listener = new SensorListener();
 116         NotificationEmitter emitter = (NotificationEmitter) mm;
 117         emitter.addNotificationListener(listener, null, null);
 118 
 119         Thread allocator = new AllocatorThread();
 120 
 121         // The chunk size needs to be larger than YOUNG_GEN_SIZE,
 122         // otherwise we will get intermittent failures when objects
 123         // end up in the young gen instead of the old gen.
 124         final long epsilon = 1024;
 125         chunkSize = YOUNG_GEN_SIZE + epsilon;
 126 
 127         // Now set threshold
 128         MemoryUsage mu = mpool.getUsage();
 129         newThreshold = mu.getUsed() + (chunkSize * NUM_CHUNKS);
 130 
 131         // Sanity check. Make sure the new threshold isn't too large.
 132         // Tweak the test if this fails.
 133         final long headRoom = chunkSize * 2;
 134         final long max = mu.getMax();
 135         if (max != -1 && newThreshold > max - headRoom) {
 136             throw new RuntimeException("TEST FAILED: newThreshold: " + newThreshold +
 137                     " is too near the maximum old gen size: " + max +
 138                     " used: " + mu.getUsed() + " headRoom: " + headRoom);
 139         }
 140 
 141         System.out.println("Setting threshold for " + mpool.getName() +
 142             " from " + mpool.getUsageThreshold() + " to " + newThreshold +
 143             ".  Current used = " + mu.getUsed());
 144         mpool.setUsageThreshold(newThreshold);
 145 
 146         if (mpool.getUsageThreshold() != newThreshold) {
 147             throw new RuntimeException("TEST FAILED: " +
 148                 "Threshold for Memory pool " + mpool.getName() +
 149                 "is " + mpool.getUsageThreshold() + " but expected to be" +
 150                 newThreshold);
 151         }
 152 
 153         // Start the AllocatorThread to continously allocate memory
 154         System.out.println("Starting an AllocatorThread to allocate memory.");
 155         allocator.start();
 156 
 157         try {
 158             allocator.join();
 159         } catch (InterruptedException e) {
 160             e.printStackTrace();
 161             System.out.println("Unexpected exception.");
 162             testFailed = true;
 163         }
 164 
 165         if (listenerInvoked == 0) {
 166             throw new RuntimeException("No listener is invoked");
 167         }
 168 
 169         if (testFailed)
 170             throw new RuntimeException("TEST FAILED.");
 171 
 172         System.out.println("Test passed.");
 173 
 174     }
 175 
 176     static class AllocatorThread extends Thread {
 177         private List objectPool = new ArrayList();
 178         public void run() {
 179             int iterations = 0;
 180             int numElements = (int) (chunkSize / 4); // minimal object size
 181             while (listenerInvoked == 0) {
 182                 iterations++;
 183                 if (trace) {
 184                     System.out.println("    Iteration " + iterations +
 185                         ": before allocation " +
 186                         mpool.getUsage().getUsed());
 187                 }
 188 
 189                 Object[] o = new Object[numElements];
 190                 if (iterations <= NUM_CHUNKS) {
 191                     // only hold a reference to the first NUM_CHUNKS
 192                     // allocated objects
 193                     objectPool.add(o);
 194                 }
 195 
 196                 if (trace) {
 197                     System.out.println("                " +
 198                         "  after allocation " +
 199                         mpool.getUsage().getUsed());
 200                 }
 201                 try {
 202                     Thread.sleep(100);
 203                 } catch (InterruptedException e) {
 204                     e.printStackTrace();
 205                     System.out.println("Unexpected exception.");
 206                     testFailed = true;
 207                 }
 208             }
 209 
 210             System.out.println("AllocatedThread finished memory allocation " +
 211                 " num_iteration = " + iterations);
 212         }
 213     }
 214 
 215 }