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