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 }