1 /*
2  * Copyright (c) 2016, 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 TestStressG1Humongous
26  * @key gc stress
27  * @summary Stress G1 by humongous allocations in situation near OOM
28  * @requires vm.gc.G1
29  * @requires !vm.flightRecorder
30  * @library /test/lib
31  * @modules java.base/jdk.internal.misc
32  * @run driver/timeout=1300 TestStressG1Humongous
33  */
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 import java.util.Collections;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.atomic.AtomicInteger;
40 
41 import jdk.test.lib.Platform;
42 import jdk.test.lib.Utils;
43 import jdk.test.lib.process.ProcessTools;
44 import jdk.test.lib.process.OutputAnalyzer;
45 
46 public class TestStressG1Humongous{
47 
main(String[] args)48     public static void main(String[] args) throws Exception {
49         // Limit heap size on 32-bit platforms
50         int heapSize = Platform.is32bit() ? 512 : 1024;
51         // Heap size, region size, threads, humongous size, timeout
52         run(heapSize, 4, 3, 1.1, 120);
53         run(heapSize, 16, 5, 2.1, 120);
54         run(heapSize, 32, 4, 0.6, 120);
55         run(heapSize, 1, 7, 0.6, 600);
56     }
57 
run(int heapSize, int regionSize, int threads, double humongousSize, int timeout)58     private static void run(int heapSize, int regionSize, int threads, double humongousSize, int timeout)
59             throws Exception {
60         ArrayList<String> options = new ArrayList<>();
61         Collections.addAll(options, Utils.getTestJavaOpts());
62         Collections.addAll(options,
63                 "-Xlog:gc=debug",
64                 "-Xmx" + heapSize + "m",
65                 "-XX:+UseG1GC",
66                 "-XX:G1HeapRegionSize=" + regionSize + "m",
67                 "-Dtimeout=" + timeout,
68                 "-Dthreads=" + threads,
69                 "-Dhumongoussize=" + humongousSize,
70                 "-Dregionsize=" + regionSize,
71                 TestStressG1HumongousImpl.class.getName()
72         );
73         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(options.toArray(new String[options.size()]));
74         OutputAnalyzer output = new OutputAnalyzer(pb.start());
75         output.shouldHaveExitValue(0);
76     }
77 }
78 
79 class TestStressG1HumongousImpl {
80     // Timeout in seconds
81     private static final int TIMEOUT = Integer.getInteger("timeout", 60);
82     private static final int THREAD_COUNT = Integer.getInteger("threads", 2);
83     private static final int REGION_SIZE = Integer.getInteger("regionsize", 1) * 1024 * 1024;
84     private static final int HUMONGOUS_SIZE = (int) (REGION_SIZE * Double.parseDouble(System.getProperty("humongoussize", "1.5")));
85     private static final int NUMBER_OF_FREE_REGIONS = 2;
86 
87     private volatile boolean isRunning;
88     private final Thread[] threads;
89     private final AtomicInteger alocatedObjectsCount;
90     private CountDownLatch countDownLatch;
91     public static final List<Object> GARBAGE = Collections.synchronizedList(new ArrayList<>());
92 
main(String[] args)93     public static void main(String[] args) throws InterruptedException {
94         new TestStressG1HumongousImpl().run();
95     }
96 
TestStressG1HumongousImpl()97     public TestStressG1HumongousImpl() {
98         isRunning = true;
99         threads = new Thread[THREAD_COUNT];
100         alocatedObjectsCount = new AtomicInteger(0);
101     }
102 
run()103     private void run() throws InterruptedException {
104         new Thread(new Timer()).start();
105         int checkedAmountOfHObjects = getExpectedAmountOfObjects();
106         while (isRunning()) {
107             countDownLatch = new CountDownLatch(THREAD_COUNT);
108             startAllocationThreads(checkedAmountOfHObjects);
109             countDownLatch.await();
110             GARBAGE.clear();
111             System.out.println("Allocated " + alocatedObjectsCount.get() + " objects.");
112             alocatedObjectsCount.set(0);
113         }
114         System.out.println("Done!");
115     }
116 
117     /**
118      * Tries to fill available memory with humongous objects to get expected amount.
119      * @return expected amount of humongous objects
120      */
getExpectedAmountOfObjects()121     private int getExpectedAmountOfObjects() {
122         long maxMem = Runtime.getRuntime().maxMemory();
123         int expectedHObjects = (int) (maxMem / HUMONGOUS_SIZE);
124         // Will allocate NUMBER_OF_FREE_REGIONS region less to give some free space for VM.
125         int checkedAmountOfHObjects = checkHeapCapacity(expectedHObjects) - NUMBER_OF_FREE_REGIONS;
126         if (checkedAmountOfHObjects <= 0) {
127             throw new RuntimeException("Cannot start testing because selected maximum heap "
128                     + "is not large enough to contain more than " + NUMBER_OF_FREE_REGIONS + " regions");
129         }
130         return checkedAmountOfHObjects;
131     }
132 
133     /**
134      * Starts several threads to allocate the requested amount of humongous objects.
135      * @param totalObjects total amount of object that will be created
136      */
startAllocationThreads(int totalObjects)137     private void startAllocationThreads(int totalObjects) {
138         int objectsPerThread = totalObjects / THREAD_COUNT;
139         int objectsForLastThread = objectsPerThread + totalObjects % THREAD_COUNT;
140         for (int i = 0; i < THREAD_COUNT - 1; ++i) {
141             threads[i] = new Thread(new AllocationThread(countDownLatch, objectsPerThread, alocatedObjectsCount));
142         }
143         threads[THREAD_COUNT - 1] = new Thread(new AllocationThread(countDownLatch, objectsForLastThread, alocatedObjectsCount));
144         for (int i = 0; i < THREAD_COUNT; ++i) {
145             threads[i].start();
146         }
147     }
148 
149     /**
150      * Creates a humongous object of the predefined size.
151      */
createObject()152     private void createObject() {
153         GARBAGE.add(new byte[HUMONGOUS_SIZE]);
154     }
155 
156     /**
157      * Tries to create the requested amount of humongous objects.
158      * In case of OOME, stops creating and cleans the created garbage.
159      * @param expectedObjects amount of objects based on heap size
160      * @return amount of created objects
161      */
checkHeapCapacity(int expectedObjects)162     private int checkHeapCapacity(int expectedObjects) {
163         int allocated = 0;
164         try {
165             while (isRunning() && allocated < expectedObjects) {
166                 createObject();
167                 ++allocated;
168             }
169         } catch (OutOfMemoryError oome) {
170             GARBAGE.clear();
171         }
172         return allocated;
173     }
174 
setDone()175     private void setDone() {
176         isRunning = false;
177     }
178 
isRunning()179     private boolean isRunning() {
180         return isRunning;
181     }
182 
183     /**
184      * Thread which allocates requested amount of humongous objects.
185      */
186     private class AllocationThread implements Runnable {
187 
188         private final int totalObjects;
189         private final CountDownLatch cdl;
190         private final AtomicInteger allocationCounter;
191 
192         /**
193          * Creates allocation thread
194          * @param cdl CountDownLatch
195          * @param objects amount of objects to allocate
196          * @param counter
197          */
AllocationThread(CountDownLatch cdl, int objects, AtomicInteger counter)198         public AllocationThread(CountDownLatch cdl, int objects, AtomicInteger counter) {
199             totalObjects = objects;
200             this.cdl = cdl;
201             allocationCounter = counter;
202         }
203 
204         @Override
run()205         public void run() {
206             int allocatedObjects = 0;
207             try {
208                 while (isRunning && allocatedObjects < totalObjects) {
209                     createObject();
210                     allocatedObjects++;
211                     allocationCounter.incrementAndGet();
212                 }
213 
214             } catch (OutOfMemoryError oome) {
215                 GARBAGE.clear();
216                 System.out.print("OOME was caught.");
217                 System.out.println(" Allocated in thread: " + allocatedObjects + " . Totally allocated: " + allocationCounter.get() + ".");
218             } finally {
219                 cdl.countDown();
220             }
221         }
222     }
223 
224     /**
225      * Simple Runnable which waits TIMEOUT and sets isRunning to false.
226      */
227     class Timer implements Runnable {
228 
229         @Override
run()230         public void run() {
231             try {
232                 Thread.sleep(TIMEOUT * 1000);
233             } catch (InterruptedException ex) {
234             }
235             setDone();
236         }
237     }
238 }
239