Wed, 31 Jul 2019 14:28:51 -0400
8048556: Unnecessary GCLocker-initiated young GCs
Summary: Fixed recognition of unnecessary GCLocker collections.
Reviewed-by: pliden, tschatzl
Contributed-by: johnc@azul.com
1.1 --- a/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp Wed Aug 07 17:00:19 2019 +0800 1.2 +++ b/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp Wed Jul 31 14:28:51 2019 -0400 1.3 @@ -2520,6 +2520,12 @@ 1.4 } 1.5 } 1.6 } 1.7 + } else if (GC_locker::should_discard(cause, gc_count_before)) { 1.8 + // Return to be consistent with VMOp failure due to another 1.9 + // collection slipping in after our gc_count but before our 1.10 + // request is processed. _gc_locker collections upgraded by 1.11 + // GCLockerInvokesConcurrent are handled above and never discarded. 1.12 + return; 1.13 } else { 1.14 if (cause == GCCause::_gc_locker || cause == GCCause::_wb_young_gc 1.15 DEBUG_ONLY(|| cause == GCCause::_scavenge_alot)) {
2.1 --- a/src/share/vm/gc_implementation/parallelScavenge/parallelScavengeHeap.cpp Wed Aug 07 17:00:19 2019 +0800 2.2 +++ b/src/share/vm/gc_implementation/parallelScavenge/parallelScavengeHeap.cpp Wed Jul 31 14:28:51 2019 -0400 2.3 @@ -530,6 +530,10 @@ 2.4 full_gc_count = Universe::heap()->total_full_collections(); 2.5 } 2.6 2.7 + if (GC_locker::should_discard(cause, gc_count)) { 2.8 + return; 2.9 + } 2.10 + 2.11 VM_ParallelGCSystemGC op(gc_count, full_gc_count, cause); 2.12 VMThread::execute(&op); 2.13 }
3.1 --- a/src/share/vm/gc_implementation/parallelScavenge/vmPSOperations.cpp Wed Aug 07 17:00:19 2019 +0800 3.2 +++ b/src/share/vm/gc_implementation/parallelScavenge/vmPSOperations.cpp Wed Jul 31 14:28:51 2019 -0400 3.3 @@ -52,11 +52,16 @@ 3.4 } 3.5 } 3.6 3.7 +static bool is_cause_full(GCCause::Cause cause) { 3.8 + return (cause != GCCause::_gc_locker) && (cause != GCCause::_wb_young_gc) 3.9 + DEBUG_ONLY(&& (cause != GCCause::_scavenge_alot)); 3.10 +} 3.11 + 3.12 // Only used for System.gc() calls 3.13 VM_ParallelGCSystemGC::VM_ParallelGCSystemGC(uint gc_count, 3.14 uint full_gc_count, 3.15 GCCause::Cause gc_cause) : 3.16 - VM_GC_Operation(gc_count, gc_cause, full_gc_count, true /* full */) 3.17 + VM_GC_Operation(gc_count, gc_cause, full_gc_count, is_cause_full(gc_cause)) 3.18 { 3.19 } 3.20 3.21 @@ -68,8 +73,7 @@ 3.22 "must be a ParallelScavengeHeap"); 3.23 3.24 GCCauseSetter gccs(heap, _gc_cause); 3.25 - if (_gc_cause == GCCause::_gc_locker || _gc_cause == GCCause::_wb_young_gc 3.26 - DEBUG_ONLY(|| _gc_cause == GCCause::_scavenge_alot)) { 3.27 + if (!_full) { 3.28 // If (and only if) the scavenge fails, this will invoke a full gc. 3.29 heap->invoke_scavenge(); 3.30 } else {
4.1 --- a/src/share/vm/gc_implementation/shared/vmGCOperations.cpp Wed Aug 07 17:00:19 2019 +0800 4.2 +++ b/src/share/vm/gc_implementation/shared/vmGCOperations.cpp Wed Jul 31 14:28:51 2019 -0400 4.3 @@ -201,6 +201,19 @@ 4.4 } 4.5 } 4.6 4.7 +static bool is_full_gc(int max_level) { 4.8 + // Return true if max_level is all generations 4.9 + return (max_level == (GenCollectedHeap::heap()->n_gens() - 1)); 4.10 +} 4.11 + 4.12 +VM_GenCollectFull::VM_GenCollectFull(uint gc_count_before, 4.13 + uint full_gc_count_before, 4.14 + GCCause::Cause gc_cause, 4.15 + int max_level) : 4.16 + VM_GC_Operation(gc_count_before, gc_cause, full_gc_count_before, 4.17 + is_full_gc(max_level) /* full */), 4.18 + _max_level(max_level) { } 4.19 + 4.20 void VM_GenCollectFull::doit() { 4.21 SvcGCMarker sgcm(SvcGCMarker::FULL); 4.22
5.1 --- a/src/share/vm/gc_implementation/shared/vmGCOperations.hpp Wed Aug 07 17:00:19 2019 +0800 5.2 +++ b/src/share/vm/gc_implementation/shared/vmGCOperations.hpp Wed Jul 31 14:28:51 2019 -0400 5.3 @@ -201,9 +201,7 @@ 5.4 VM_GenCollectFull(uint gc_count_before, 5.5 uint full_gc_count_before, 5.6 GCCause::Cause gc_cause, 5.7 - int max_level) 5.8 - : VM_GC_Operation(gc_count_before, gc_cause, full_gc_count_before, true /* full */), 5.9 - _max_level(max_level) { } 5.10 + int max_level); 5.11 ~VM_GenCollectFull() {} 5.12 virtual VMOp_Type type() const { return VMOp_GenCollectFull; } 5.13 virtual void doit();
6.1 --- a/src/share/vm/memory/gcLocker.cpp Wed Aug 07 17:00:19 2019 +0800 6.2 +++ b/src/share/vm/memory/gcLocker.cpp Wed Jul 31 14:28:51 2019 -0400 6.3 @@ -31,6 +31,7 @@ 6.4 volatile jint GC_locker::_jni_lock_count = 0; 6.5 volatile bool GC_locker::_needs_gc = false; 6.6 volatile bool GC_locker::_doing_gc = false; 6.7 +unsigned int GC_locker::_total_collections = 0; 6.8 6.9 #ifdef ASSERT 6.10 volatile jint GC_locker::_debug_jni_lock_count = 0; 6.11 @@ -94,6 +95,11 @@ 6.12 } 6.13 } 6.14 6.15 +bool GC_locker::should_discard(GCCause::Cause cause, uint total_collections) { 6.16 + return (cause == GCCause::_gc_locker) && 6.17 + (_total_collections != total_collections); 6.18 +} 6.19 + 6.20 void GC_locker::jni_lock(JavaThread* thread) { 6.21 assert(!thread->in_critical(), "shouldn't currently be in a critical region"); 6.22 MutexLocker mu(JNICritical_lock); 6.23 @@ -117,7 +123,13 @@ 6.24 decrement_debug_jni_lock_count(); 6.25 thread->exit_critical(); 6.26 if (needs_gc() && !is_active_internal()) { 6.27 - // We're the last thread out. Cause a GC to occur. 6.28 + // We're the last thread out. Request a GC. 6.29 + // Capture the current total collections, to allow detection of 6.30 + // other collections that make this one unnecessary. The value of 6.31 + // total_collections() is only changed at a safepoint, so there 6.32 + // must not be a safepoint between the lock becoming inactive and 6.33 + // getting the count, else there may be unnecessary GCLocker GCs. 6.34 + _total_collections = Universe::heap()->total_collections(); 6.35 _doing_gc = true; 6.36 { 6.37 // Must give up the lock while at a safepoint
7.1 --- a/src/share/vm/memory/gcLocker.hpp Wed Aug 07 17:00:19 2019 +0800 7.2 +++ b/src/share/vm/memory/gcLocker.hpp Wed Jul 31 14:28:51 2019 -0400 7.3 @@ -26,6 +26,7 @@ 7.4 #define SHARE_VM_MEMORY_GCLOCKER_HPP 7.5 7.6 #include "gc_interface/collectedHeap.hpp" 7.7 +#include "gc_interface/gcCause.hpp" 7.8 #include "memory/genCollectedHeap.hpp" 7.9 #include "memory/universe.hpp" 7.10 #include "oops/oop.hpp" 7.11 @@ -57,6 +58,7 @@ 7.12 static volatile bool _needs_gc; // heap is filling, we need a GC 7.13 // note: bool is typedef'd as jint 7.14 static volatile bool _doing_gc; // unlock_critical() is doing a GC 7.15 + static uint _total_collections; // value for _gc_locker collection 7.16 7.17 #ifdef ASSERT 7.18 // This lock count is updated for all operations and is used to 7.19 @@ -116,6 +118,12 @@ 7.20 // Sets _needs_gc if is_active() is true. Returns is_active(). 7.21 static bool check_active_before_gc(); 7.22 7.23 + // Return true if the designated collection is a GCLocker request 7.24 + // that should be discarded. Returns true if cause == GCCause::_gc_locker 7.25 + // and the given total collection value indicates a collection has been 7.26 + // done since the GCLocker request was made. 7.27 + static bool should_discard(GCCause::Cause cause, uint total_collections); 7.28 + 7.29 // Stalls the caller (who should not be in a jni critical section) 7.30 // until needs_gc() clears. Note however that needs_gc() may be 7.31 // set at a subsequent safepoint and/or cleared under the
8.1 --- a/src/share/vm/memory/genCollectedHeap.cpp Wed Aug 07 17:00:19 2019 +0800 8.2 +++ b/src/share/vm/memory/genCollectedHeap.cpp Wed Jul 31 14:28:51 2019 -0400 8.3 @@ -796,8 +796,11 @@ 8.4 #else // INCLUDE_ALL_GCS 8.5 ShouldNotReachHere(); 8.6 #endif // INCLUDE_ALL_GCS 8.7 - } else if (cause == GCCause::_wb_young_gc) { 8.8 - // minor collection for WhiteBox API 8.9 + } else if ((cause == GCCause::_wb_young_gc) || 8.10 + (cause == GCCause::_gc_locker)) { 8.11 + // minor collection for WhiteBox or GCLocker. 8.12 + // _gc_locker collections upgraded by GCLockerInvokesConcurrent 8.13 + // are handled above and never discarded. 8.14 collect(cause, 0); 8.15 } else { 8.16 #ifdef ASSERT 8.17 @@ -835,6 +838,11 @@ 8.18 // Read the GC count while holding the Heap_lock 8.19 unsigned int gc_count_before = total_collections(); 8.20 unsigned int full_gc_count_before = total_full_collections(); 8.21 + 8.22 + if (GC_locker::should_discard(cause, gc_count_before)) { 8.23 + return; 8.24 + } 8.25 + 8.26 { 8.27 MutexUnlocker mu(Heap_lock); // give up heap lock, execute gets it back 8.28 VM_GenCollectFull op(gc_count_before, full_gc_count_before, 8.29 @@ -887,24 +895,16 @@ 8.30 8.31 void GenCollectedHeap::do_full_collection(bool clear_all_soft_refs, 8.32 int max_level) { 8.33 - int local_max_level; 8.34 - if (!incremental_collection_will_fail(false /* don't consult_young */) && 8.35 - gc_cause() == GCCause::_gc_locker) { 8.36 - local_max_level = 0; 8.37 - } else { 8.38 - local_max_level = max_level; 8.39 - } 8.40 8.41 do_collection(true /* full */, 8.42 clear_all_soft_refs /* clear_all_soft_refs */, 8.43 0 /* size */, 8.44 false /* is_tlab */, 8.45 - local_max_level /* max_level */); 8.46 + max_level /* max_level */); 8.47 // Hack XXX FIX ME !!! 8.48 // A scavenge may not have been attempted, or may have 8.49 // been attempted and failed, because the old gen was too full 8.50 - if (local_max_level == 0 && gc_cause() == GCCause::_gc_locker && 8.51 - incremental_collection_will_fail(false /* don't consult_young */)) { 8.52 + if (gc_cause() == GCCause::_gc_locker && incremental_collection_failed()) { 8.53 if (PrintGCDetails) { 8.54 gclog_or_tty->print_cr("GC locker: Trying a full collection " 8.55 "because scavenge failed");
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/test/gc/stress/gclocker/TestExcessGCLockerCollections.java Wed Jul 31 14:28:51 2019 -0400 9.3 @@ -0,0 +1,285 @@ 9.4 +/* 9.5 + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 9.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 9.7 + * 9.8 + * This code is free software; you can redistribute it and/or modify it 9.9 + * under the terms of the GNU General Public License version 2 only, as 9.10 + * published by the Free Software Foundation. 9.11 + * 9.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 9.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 9.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 9.15 + * version 2 for more details (a copy is included in the LICENSE file that 9.16 + * accompanied this code). 9.17 + * 9.18 + * You should have received a copy of the GNU General Public License version 9.19 + * 2 along with this work; if not, write to the Free Software Foundation, 9.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 9.21 + * 9.22 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 9.23 + * or visit www.oracle.com if you need additional information or have any 9.24 + * questions. 9.25 + */ 9.26 + 9.27 +package gc.stress.gclocker; 9.28 + 9.29 +// Based on Kim Barrett;s test for JDK-8048556 9.30 + 9.31 +/* 9.32 + * @test TestExcessGCLockerCollections 9.33 + * @key gc 9.34 + * @bug 8048556 9.35 + * @summary Check for GC Locker initiated GCs that immediately follow another 9.36 + * GC and so have very little needing to be collected. 9.37 + * @library /testlibrary 9.38 + * @run driver/timeout=1000 gc.stress.gclocker.TestExcessGCLockerCollections 300 4 2 9.39 + */ 9.40 + 9.41 +import java.util.HashMap; 9.42 +import java.util.Map; 9.43 + 9.44 +import java.util.zip.Deflater; 9.45 + 9.46 +import java.util.ArrayList; 9.47 +import java.util.Arrays; 9.48 + 9.49 +import javax.management.MBeanServer; 9.50 +import javax.management.Notification; 9.51 +import javax.management.NotificationListener; 9.52 +import javax.management.openmbean.CompositeData; 9.53 +import java.lang.management.ManagementFactory; 9.54 +import java.lang.management.GarbageCollectorMXBean; 9.55 +import java.lang.management.MemoryUsage; 9.56 +import java.util.List; 9.57 +import com.sun.management.GarbageCollectionNotificationInfo; 9.58 +import com.sun.management.GcInfo; 9.59 + 9.60 +import com.oracle.java.testlibrary.Asserts; 9.61 +import com.oracle.java.testlibrary.ProcessTools; 9.62 +import com.oracle.java.testlibrary.OutputAnalyzer; 9.63 + 9.64 +class TestExcessGCLockerCollectionsStringConstants { 9.65 + // Some constant strings used in both GC logging and error detection 9.66 + static public final String GCLOCKER_CAUSE = "GCLocker Initiated GC"; 9.67 + static public final String USED_TOO_LOW = "TOO LOW"; 9.68 + static public final String USED_OK = "OK"; 9.69 +} 9.70 + 9.71 +class TestExcessGCLockerCollectionsAux { 9.72 + static private final int LARGE_MAP_SIZE = 64 * 1024; 9.73 + 9.74 + static private final int MAP_ARRAY_LENGTH = 4; 9.75 + static private final int MAP_SIZE = 1024; 9.76 + 9.77 + static private final int BYTE_ARRAY_LENGTH = 128 * 1024; 9.78 + 9.79 + static private void println(String str) { System.out.println(str); } 9.80 + static private void println() { System.out.println(); } 9.81 + 9.82 + static private volatile boolean keepRunning = true; 9.83 + 9.84 + static Map<Integer,String> populateMap(int size) { 9.85 + Map<Integer,String> map = new HashMap<Integer,String>(); 9.86 + for (int i = 0; i < size; i += 1) { 9.87 + Integer keyInt = Integer.valueOf(i); 9.88 + String valStr = "value is [" + i + "]"; 9.89 + map.put(keyInt,valStr); 9.90 + } 9.91 + return map; 9.92 + } 9.93 + 9.94 + static private class AllocatingWorker implements Runnable { 9.95 + private final Object[] array = new Object[MAP_ARRAY_LENGTH]; 9.96 + private int arrayIndex = 0; 9.97 + 9.98 + private void doStep() { 9.99 + Map<Integer,String> map = populateMap(MAP_SIZE); 9.100 + array[arrayIndex] = map; 9.101 + arrayIndex = (arrayIndex + 1) % MAP_ARRAY_LENGTH; 9.102 + } 9.103 + 9.104 + public void run() { 9.105 + while (keepRunning) { 9.106 + doStep(); 9.107 + } 9.108 + } 9.109 + } 9.110 + 9.111 + static private class JNICriticalWorker implements Runnable { 9.112 + private int count; 9.113 + 9.114 + private void doStep() { 9.115 + byte[] inputArray = new byte[BYTE_ARRAY_LENGTH]; 9.116 + for (int i = 0; i < inputArray.length; i += 1) { 9.117 + inputArray[i] = (byte) (count + i); 9.118 + } 9.119 + 9.120 + Deflater deflater = new Deflater(); 9.121 + deflater.setInput(inputArray); 9.122 + deflater.finish(); 9.123 + 9.124 + byte[] outputArray = new byte[2 * inputArray.length]; 9.125 + deflater.deflate(outputArray); 9.126 + 9.127 + count += 1; 9.128 + } 9.129 + 9.130 + public void run() { 9.131 + while (keepRunning) { 9.132 + doStep(); 9.133 + } 9.134 + } 9.135 + } 9.136 + 9.137 + static class GCNotificationListener implements NotificationListener { 9.138 + static private final double MIN_USED_PERCENT = 40.0; 9.139 + 9.140 + static private final List<String> newGenPoolNames = Arrays.asList( 9.141 + "G1 Eden Space", // OpenJDK G1GC: -XX:+UseG1GC 9.142 + "PS Eden Space", // OpenJDK ParallelGC: -XX:+ParallelGC 9.143 + "Par Eden Space", // OpenJDK ConcMarkSweepGC: -XX:+ConcMarkSweepGC 9.144 + "Eden Space" // OpenJDK SerialGC: -XX:+UseSerialGC 9.145 + // OpenJDK ConcMarkSweepGC: -XX:+ConcMarkSweepGC -XX:-UseParNewGC 9.146 + ); 9.147 + 9.148 + @Override 9.149 + public void handleNotification(Notification notification, Object handback) { 9.150 + try { 9.151 + if (notification.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)) { 9.152 + GarbageCollectionNotificationInfo info = 9.153 + GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); 9.154 + 9.155 + String gc_cause = info.getGcCause(); 9.156 + 9.157 + if (gc_cause.equals(TestExcessGCLockerCollectionsStringConstants.GCLOCKER_CAUSE)) { 9.158 + Map<String, MemoryUsage> memory_before_gc = info.getGcInfo().getMemoryUsageBeforeGc(); 9.159 + 9.160 + for (String newGenPoolName : newGenPoolNames) { 9.161 + MemoryUsage usage = memory_before_gc.get(newGenPoolName); 9.162 + if (usage == null) continue; 9.163 + 9.164 + double startTime = ((double) info.getGcInfo().getStartTime()) / 1000.0; 9.165 + long used = usage.getUsed(); 9.166 + long committed = usage.getCommitted(); 9.167 + long max = usage.getMax(); 9.168 + double used_percent = (((double) used) / Math.max(committed, max)) * 100.0; 9.169 + 9.170 + System.out.printf("%6.3f: (%s) %d/%d/%d, %8.4f%% (%s)\n", 9.171 + startTime, gc_cause, used, committed, max, used_percent, 9.172 + ((used_percent < MIN_USED_PERCENT) ? TestExcessGCLockerCollectionsStringConstants.USED_TOO_LOW 9.173 + : TestExcessGCLockerCollectionsStringConstants.USED_OK)); 9.174 + } 9.175 + } 9.176 + } 9.177 + } catch (RuntimeException ex) { 9.178 + System.err.println("Exception during notification processing:" + ex); 9.179 + ex.printStackTrace(); 9.180 + } 9.181 + } 9.182 + 9.183 + public static boolean register() { 9.184 + try { 9.185 + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); 9.186 + 9.187 + // Get the list of MX 9.188 + List<GarbageCollectorMXBean> gc_mxbeans = ManagementFactory.getGarbageCollectorMXBeans(); 9.189 + 9.190 + // Create the notification listener 9.191 + GCNotificationListener gcNotificationListener = new GCNotificationListener(); 9.192 + 9.193 + for (GarbageCollectorMXBean gcbean : gc_mxbeans) { 9.194 + // Add notification listener for the MXBean 9.195 + mbeanServer.addNotificationListener(gcbean.getObjectName(), gcNotificationListener, null, null); 9.196 + } 9.197 + } catch (Exception ex) { 9.198 + System.err.println("Exception during mbean registration:" + ex); 9.199 + ex.printStackTrace(); 9.200 + // We've failed to set up, terminate 9.201 + return false; 9.202 + } 9.203 + 9.204 + return true; 9.205 + } 9.206 + } 9.207 + 9.208 + static public Map<Integer,String> largeMap; 9.209 + 9.210 + static public void main(String args[]) { 9.211 + long durationSec = Long.parseLong(args[0]); 9.212 + int allocThreadNum = Integer.parseInt(args[1]); 9.213 + int jniCriticalThreadNum = Integer.parseInt(args[2]); 9.214 + 9.215 + println("Running for " + durationSec + " secs"); 9.216 + 9.217 + if (!GCNotificationListener.register()) { 9.218 + println("failed to register GC notification listener"); 9.219 + System.exit(-1); 9.220 + } 9.221 + 9.222 + largeMap = populateMap(LARGE_MAP_SIZE); 9.223 + 9.224 + println("Starting " + allocThreadNum + " allocating threads"); 9.225 + for (int i = 0; i < allocThreadNum; i += 1) { 9.226 + new Thread(new AllocatingWorker()).start(); 9.227 + } 9.228 + 9.229 + println("Starting " + jniCriticalThreadNum + " jni critical threads"); 9.230 + for (int i = 0; i < jniCriticalThreadNum; i += 1) { 9.231 + new Thread(new JNICriticalWorker()).start(); 9.232 + } 9.233 + 9.234 + long durationMS = (long) (1000 * durationSec); 9.235 + long start = System.currentTimeMillis(); 9.236 + long now = start; 9.237 + long soFar = now - start; 9.238 + while (soFar < durationMS) { 9.239 + try { 9.240 + Thread.sleep(durationMS - soFar); 9.241 + } catch (Exception e) { 9.242 + } 9.243 + now = System.currentTimeMillis(); 9.244 + soFar = now - start; 9.245 + } 9.246 + println("Done."); 9.247 + keepRunning = false; 9.248 + } 9.249 +} 9.250 + 9.251 +public class TestExcessGCLockerCollections { 9.252 + private static final String USED_OK_LINE = 9.253 + "\\(" + TestExcessGCLockerCollectionsStringConstants.GCLOCKER_CAUSE + "\\)" 9.254 + + " .* " + 9.255 + "\\(" + TestExcessGCLockerCollectionsStringConstants.USED_OK + "\\)"; 9.256 + private static final String USED_TOO_LOW_LINE = 9.257 + "\\(" + TestExcessGCLockerCollectionsStringConstants.GCLOCKER_CAUSE + "\\)" 9.258 + + " .* " + 9.259 + "\\(" + TestExcessGCLockerCollectionsStringConstants.USED_TOO_LOW + "\\)"; 9.260 + 9.261 + private static final String[] COMMON_OPTIONS = new String[] { 9.262 + "-Xmx1G", "-Xms1G", "-Xmn256M" }; 9.263 + 9.264 + public static void main(String args[]) throws Exception { 9.265 + if (args.length < 3) { 9.266 + System.out.println("usage: TestExcessGCLockerCollections" + 9.267 + " <duration sec> <alloc threads>" + 9.268 + " <jni critical threads>"); 9.269 + throw new RuntimeException("Invalid arguments"); 9.270 + } 9.271 + 9.272 + ArrayList<String> finalArgs = new ArrayList<String>(); 9.273 + finalArgs.addAll(Arrays.asList(COMMON_OPTIONS)); 9.274 + finalArgs.add(TestExcessGCLockerCollectionsAux.class.getName()); 9.275 + finalArgs.addAll(Arrays.asList(args)); 9.276 + 9.277 + // GC and other options obtained from test framework. 9.278 + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( 9.279 + true, finalArgs.toArray(new String[0])); 9.280 + OutputAnalyzer output = new OutputAnalyzer(pb.start()); 9.281 + output.shouldHaveExitValue(0); 9.282 + //System.out.println("------------- begin stdout ----------------"); 9.283 + //System.out.println(output.getStdout()); 9.284 + //System.out.println("------------- end stdout ----------------"); 9.285 + output.stdoutShouldMatch(USED_OK_LINE); 9.286 + output.stdoutShouldNotMatch(USED_TOO_LOW_LINE); 9.287 + } 9.288 +}