Path: blob/master/test/jdk/java/lang/management/MemoryMXBean/LowMemoryTest.java
41155 views
/*1* Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 453053826* @summary Basic unit test of memory management testing:27* 1) setUsageThreshold() and getUsageThreshold()28* 2) test low memory detection on the old generation.29* @author Mandy Chung30*31* @requires vm.gc == "null"32* @requires vm.opt.ExplicitGCInvokesConcurrent != "true"33* @requires vm.opt.DisableExplicitGC != "true"34* @library /test/lib35*36* @build LowMemoryTest MemoryUtil RunUtil37* @build sun.hotspot.WhiteBox38* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox39* @run main/othervm/timeout=600 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. LowMemoryTest40*/4142import java.lang.management.*;43import java.util.*;44import java.util.concurrent.Phaser;45import javax.management.*;46import javax.management.openmbean.CompositeData;47import jdk.test.lib.JDKToolFinder;48import jdk.test.lib.process.ProcessTools;49import jdk.test.lib.Utils;5051import sun.hotspot.code.Compiler;5253public class LowMemoryTest {54private static final MemoryMXBean mm = ManagementFactory.getMemoryMXBean();55private static final List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();56private static final Phaser phaser = new Phaser(2);57private static MemoryPoolMXBean mpool = null;58private static boolean trace = false;59private static boolean testFailed = false;60private static final int NUM_TRIGGERS = 5;61private static final int NUM_CHUNKS = 2;62private static final int YOUNG_GEN_SIZE = 8 * 1024 * 1024;63private static long chunkSize;64private static final String classMain = "LowMemoryTest$TestMain";6566/**67* Run the test multiple times with different GC versions.68* First with default command line specified by the framework.69* Then with GC versions specified by the test.70*/71public static void main(String a[]) throws Throwable {72// Use a low young gen size to ensure that the73// allocated objects are put in the old gen.74final String nmFlag = "-Xmn" + YOUNG_GEN_SIZE;75// Using large pages will change the young gen size,76// make sure we don't use them for this test.77final String lpFlag = "-XX:-UseLargePages";78// Prevent G1 from selecting a large heap region size,79// since that would change the young gen size.80final String g1Flag = "-XX:G1HeapRegionSize=1m";8182// Runs the test collecting subprocess I/O while it's running.83traceTest(classMain + ", -XX:+UseSerialGC", nmFlag, lpFlag, "-XX:+UseSerialGC");84traceTest(classMain + ", -XX:+UseParallelGC", nmFlag, lpFlag, "-XX:+UseParallelGC");85traceTest(classMain + ", -XX:+UseG1GC", nmFlag, lpFlag, "-XX:+UseG1GC", g1Flag);86}8788/*89* Creating command-line for running subprocess JVM:90*91* JVM command line is like:92* {test_jdk}/bin/java {defaultopts} -cp {test.class.path} {testopts} main93*94* {defaultopts} are the default java options set by the framework.95*96* @param testOpts java options specified by the test.97*/98private static List<String> buildCommandLine(String... testOpts) {99List<String> opts = new ArrayList<>();100opts.add(JDKToolFinder.getJDKTool("java"));101opts.addAll(Arrays.asList(Utils.getTestJavaOpts()));102opts.add("-cp");103opts.add(System.getProperty("test.class.path", "test.class.path"));104opts.add("-Xlog:gc*=debug");105opts.addAll(Arrays.asList(testOpts));106opts.add(classMain);107108return opts;109}110111/**112* Runs LowMemoryTest$TestMain with the passed options and redirects subprocess113* standard I/O to the current (parent) process. This provides a trace of what114* happens in the subprocess while it is runnning (and before it terminates).115*116* @param prefixName the prefix string for redirected outputs117* @param testOpts java options specified by the test.118*/119private static void traceTest(String prefixName,120String... testOpts)121throws Throwable {122123// Building command-line124List<String> opts = buildCommandLine(testOpts);125126// We activate all tracing in subprocess127opts.add("trace");128129// Launch separate JVM subprocess130String[] optsArray = opts.toArray(new String[0]);131ProcessBuilder pb = new ProcessBuilder(optsArray);132System.out.println("\n========= Tracing of subprocess " + prefixName + " =========");133Process p = ProcessTools.startProcess(prefixName, pb);134135// Handling end of subprocess136try {137int exitCode = p.waitFor();138if (exitCode != 0) {139throw new RuntimeException(140"Subprocess unexpected exit value of [" + exitCode + "]. Expected 0.\n");141}142} catch (InterruptedException e) {143throw new RuntimeException("Parent process interrupted with exception : \n " + e + " :" );144}145146147}148149private static volatile boolean listenerInvoked = false;150static class SensorListener implements NotificationListener {151@Override152public void handleNotification(Notification notif, Object handback) {153String type = notif.getType();154if (type.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED) ||155type.equals(MemoryNotificationInfo.156MEMORY_COLLECTION_THRESHOLD_EXCEEDED)) {157158MemoryNotificationInfo minfo = MemoryNotificationInfo.159from((CompositeData) notif.getUserData());160161MemoryUtil.printMemoryNotificationInfo(minfo, type);162listenerInvoked = true;163}164}165}166167static class TestListener implements NotificationListener {168private int triggers = 0;169private final long[] count = new long[NUM_TRIGGERS * 2];170private final long[] usedMemory = new long[NUM_TRIGGERS * 2];171172@Override173public void handleNotification(Notification notif, Object handback) {174MemoryNotificationInfo minfo = MemoryNotificationInfo.175from((CompositeData) notif.getUserData());176count[triggers] = minfo.getCount();177usedMemory[triggers] = minfo.getUsage().getUsed();178triggers++;179}180public void checkResult() throws Exception {181if (!checkValue(triggers, NUM_TRIGGERS)) {182throw new RuntimeException("Unexpected number of triggers = " +183triggers + " but expected to be " + NUM_TRIGGERS);184}185186for (int i = 0; i < triggers; i++) {187if (!checkValue(count[i], i + 1)) {188throw new RuntimeException("Unexpected count of" +189" notification #" + i +190" count = " + count[i] +191" but expected to be " + (i+1));192}193if (usedMemory[i] < newThreshold) {194throw new RuntimeException("Used memory = " +195usedMemory[i] + " is less than the threshold = " +196newThreshold);197}198}199}200201private boolean checkValue(int value, int target) {202return checkValue((long)value, target);203}204205private boolean checkValue(long value, int target) {206return value == target;207}208}209210private static long newThreshold;211212private static class TestMain {213public static void main(String args[]) throws Exception {214if (args.length > 0 && args[0].equals("trace")) {215trace = true;216}217218// Find the Old generation which supports low memory detection219ListIterator iter = pools.listIterator();220while (iter.hasNext()) {221MemoryPoolMXBean p = (MemoryPoolMXBean) iter.next();222if (p.getType() == MemoryType.HEAP &&223p.isUsageThresholdSupported()) {224mpool = p;225if (trace) {226System.out.println("Selected memory pool for low memory " +227"detection.");228MemoryUtil.printMemoryPool(mpool);229}230break;231}232}233234TestListener listener = new TestListener();235SensorListener l2 = new SensorListener();236NotificationEmitter emitter = (NotificationEmitter) mm;237emitter.addNotificationListener(listener, null, null);238emitter.addNotificationListener(l2, null, null);239240Thread allocator = new AllocatorThread();241Thread sweeper = new SweeperThread();242243// The chunk size needs to be larger than YOUNG_GEN_SIZE,244// otherwise we will get intermittent failures when objects245// end up in the young gen instead of the old gen.246final long epsilon = 1024;247chunkSize = YOUNG_GEN_SIZE + epsilon;248249MemoryUsage mu = mpool.getUsage();250newThreshold = mu.getUsed() + (chunkSize * NUM_CHUNKS);251252// Sanity check. Make sure the new threshold isn't too large.253// Tweak the test if this fails.254final long headRoom = chunkSize * 2;255final long max = mu.getMax();256if (max != -1 && newThreshold > max - headRoom) {257throw new RuntimeException("TEST FAILED: newThreshold: " + newThreshold +258" is too near the maximum old gen size: " + max +259" used: " + mu.getUsed() + " headRoom: " + headRoom);260}261262System.out.println("Setting threshold for " + mpool.getName() +263" from " + mpool.getUsageThreshold() + " to " + newThreshold +264". Current used = " + mu.getUsed());265266mpool.setUsageThreshold(newThreshold);267268if (mpool.getUsageThreshold() != newThreshold) {269throw new RuntimeException("TEST FAILED: " +270"Threshold for Memory pool " + mpool.getName() +271"is " + mpool.getUsageThreshold() + " but expected to be" +272newThreshold);273}274275276allocator.start();277// Force Allocator start first278phaser.arriveAndAwaitAdvance();279sweeper.start();280281282try {283allocator.join();284// Wait until AllocatorThread's done285phaser.arriveAndAwaitAdvance();286sweeper.join();287} catch (InterruptedException e) {288System.out.println("Unexpected exception:" + e);289testFailed = true;290}291292listener.checkResult();293294if (testFailed)295throw new RuntimeException("TEST FAILED.");296297System.out.println(RunUtil.successMessage);298}299}300301private static void goSleep(long ms) {302try {303Thread.sleep(ms);304} catch (InterruptedException e) {305System.out.println("Unexpected exception:" + e);306testFailed = true;307}308}309310private static final List<Object> objectPool = new ArrayList<>();311static class AllocatorThread extends Thread {312public void doTask() {313int iterations = 0;314int numElements = (int) (chunkSize / 4); // minimal object size315while (!listenerInvoked || mpool.getUsage().getUsed() < mpool.getUsageThreshold()) {316iterations++;317if (trace) {318System.out.println(" Iteration " + iterations +319": before allocation " +320mpool.getUsage().getUsed());321}322323Object[] o = new Object[numElements];324if (iterations <= NUM_CHUNKS) {325// only hold a reference to the first NUM_CHUNKS326// allocated objects327objectPool.add(o);328}329330if (trace) {331System.out.println(" " +332" after allocation " +333mpool.getUsage().getUsed());334}335goSleep(100);336}337}338@Override339public void run() {340for (int i = 1; i <= NUM_TRIGGERS; i++) {341// Sync with SweeperThread's second phase.342phaser.arriveAndAwaitAdvance();343System.out.println("AllocatorThread is doing task " + i +344" phase " + phaser.getPhase());345doTask();346// Sync with SweeperThread's first phase.347phaser.arriveAndAwaitAdvance();348System.out.println("AllocatorThread done task " + i +349" phase " + phaser.getPhase());350if (testFailed) {351return;352}353}354}355}356357static class SweeperThread extends Thread {358private void doTask() {359int iterations = 0;360if (trace) {361System.out.println("SweeperThread clearing allocated objects.");362}363364for (; mpool.getUsage().getUsed() >=365mpool.getUsageThreshold();) {366// clear all allocated objects and invoke GC367objectPool.clear();368mm.gc();369370if (trace) {371iterations++;372System.out.println("SweeperThread called " + iterations +373" time(s) MemoryMXBean.gc().");374}375376goSleep(100);377}378}379380@Override381public void run() {382for (int i = 1; i <= NUM_TRIGGERS; i++) {383// Sync with AllocatorThread's first phase.384phaser.arriveAndAwaitAdvance();385System.out.println("SweeperThread is doing task " + i +386" phase " + phaser.getPhase());387388doTask();389390listenerInvoked = false;391392// Sync with AllocatorThread's second phase.393phaser.arriveAndAwaitAdvance();394System.out.println("SweeperThread done task " + i +395" phase " + phaser.getPhase());396if (testFailed) return;397}398}399}400}401402403