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
  27  * @key stress
  28  * @summary Stress G1 by humongous allocations in situation near OOM
  29  * @requires vm.gc=="G1" | vm.gc=="null"
  30  * @run main/othervm/timeout=200 -Xlog:gc=debug -Xmx1g -XX:+UseG1GC -XX:G1HeapRegionSize=4m
  31  *              -Dtimeout=120 -Dthreads=3 -Dhumongoussize=1.1 -Dregionsize=4 TestStressG1Humongous
  32  * @run main/othervm/timeout=200 -Xlog:gc=debug -Xmx1g -XX:+UseG1GC -XX:G1HeapRegionSize=16m
  33  *              -Dtimeout=120 -Dthreads=5 -Dhumongoussize=2.1 -Dregionsize=16 TestStressG1Humongous
  34  * @run main/othervm/timeout=200 -Xlog:gc=debug -Xmx1g -XX:+UseG1GC -XX:G1HeapRegionSize=32m
  35  *              -Dtimeout=120 -Dthreads=4 -Dhumongoussize=0.6 -Dregionsize=32 TestStressG1Humongous
  36  * @run main/othervm/timeout=700 -Xlog:gc=debug -Xmx1g -XX:+UseG1GC -XX:G1HeapRegionSize=1m
  37  *              -Dtimeout=600 -Dthreads=7 -Dhumongoussize=0.6 -Dregionsize=1 TestStressG1Humongous
  38  */
  39 
  40 import java.util.ArrayList;
  41 import java.util.List;
  42 import java.util.Collections;
  43 import java.util.concurrent.CountDownLatch;
  44 import java.util.concurrent.ExecutorService;
  45 import java.util.concurrent.Executors;
  46 import java.util.concurrent.atomic.AtomicInteger;
  47 
  48 public class TestStressG1Humongous {
  49 
  50     // Timeout in seconds
  51     private static final int TIMEOUT = Integer.getInteger("timeout", 60);
  52     private static final int THREAD_COUNT = Integer.getInteger("threads", 2);
  53     private static final int REGION_SIZE = Integer.getInteger("regionsize", 1) * 1024 * 1024;
  54     private static final int HUMONGOUS_SIZE = (int) (REGION_SIZE * Double.parseDouble(System.getProperty("humongoussize", "1.5")));
  55 
  56     private volatile boolean isRunning;
  57     private final ExecutorService threadExecutor;
  58     private final AtomicInteger alocatedObjectsCount;
  59     private CountDownLatch countDownLatch;
  60     public static final List<Object> GARBAGE = Collections.synchronizedList(new ArrayList<>());
  61 
  62     public static void main(String[] args) throws InterruptedException {
  63         new TestStressG1Humongous().run();
  64     }
  65 
  66     public TestStressG1Humongous() {
  67         isRunning = true;
  68         threadExecutor = Executors.newFixedThreadPool(THREAD_COUNT + 1);
  69         alocatedObjectsCount = new AtomicInteger(0);
  70     }
  71 
  72     private void run() throws InterruptedException {
  73         threadExecutor.submit(new Timer());
  74         int checkedAmountOfHObjects = getExpectedAmountOfObjects();
  75         while (isRunning()) {
  76             countDownLatch = new CountDownLatch(THREAD_COUNT);
  77             startAllocationThreads(checkedAmountOfHObjects);
  78             countDownLatch.await();
  79             GARBAGE.clear();
  80             System.out.println("Allocated " + alocatedObjectsCount.get() + " objects.");
  81             alocatedObjectsCount.set(0);
  82         }
  83         threadExecutor.shutdown();
  84         System.out.println("Done!");
  85     }
  86 
  87     /**
  88      * Tries to fill available memory with humongous objects to get expected amount.
  89      * @return expected amount of humongous objects
  90      */
  91     private int getExpectedAmountOfObjects() {
  92         long maxMem = Runtime.getRuntime().maxMemory();
  93         int expectedHObjects = (int) (maxMem / HUMONGOUS_SIZE);
  94         // Will allocate 1 region less to give some free space for VM.
  95         int checkedAmountOfHObjects = checkHeapCapacity(expectedHObjects) - 1;
  96         return checkedAmountOfHObjects;
  97     }
  98 
  99     /**
 100      * Starts several threads to allocate the requested amount of humongous objects.
 101      * @param totalObjects total amount of object that will be created
 102      */
 103     private void startAllocationThreads(int totalObjects) {
 104         int objectsPerThread = totalObjects / THREAD_COUNT;
 105         int objectsForLastThread = objectsPerThread + totalObjects % THREAD_COUNT;
 106         for (int i = 0; i < THREAD_COUNT - 1; ++i) {
 107             threadExecutor.submit(new AllocationThread(countDownLatch, objectsPerThread, alocatedObjectsCount));
 108         }
 109         threadExecutor.submit(new AllocationThread(countDownLatch, objectsForLastThread, alocatedObjectsCount));
 110     }
 111 
 112     /**
 113      * Creates a humongous object of the predefined size.
 114      */
 115     private void createObject() {
 116         GARBAGE.add(new byte[HUMONGOUS_SIZE]);
 117     }
 118 
 119     /**
 120      * Tries to create the requested amount of humongous objects.
 121      * In case of OOME, stops creating and cleans the created garbage.
 122      * @param expectedObjects amount of objects based on heap size
 123      * @return amount of created objects
 124      */
 125     private int checkHeapCapacity(int expectedObjects) {
 126         int allocated = 0;
 127         try {
 128             while (isRunning() && allocated < expectedObjects) {
 129                 createObject();
 130                 ++allocated;
 131             }
 132         } catch (OutOfMemoryError oome) {
 133             GARBAGE.clear();
 134         }
 135         return allocated;
 136     }
 137 
 138     private void setDone() {
 139         isRunning = false;
 140     }
 141 
 142     private boolean isRunning() {
 143         return isRunning;
 144     }
 145 
 146     /**
 147      * Thread which allocates requested amount of humongous objects.
 148      */
 149     private class AllocationThread implements Runnable {
 150 
 151         private final int totalObjects;
 152         private final CountDownLatch cdl;
 153         private final AtomicInteger allocationCounter;
 154 
 155         /**
 156          * Creates allocation thread
 157          * @param cdl CountDownLatch
 158          * @param objects amount of objects to allocate
 159          * @param counter
 160          */
 161         public AllocationThread(CountDownLatch cdl, int objects, AtomicInteger counter) {
 162             totalObjects = objects;
 163             this.cdl = cdl;
 164             allocationCounter = counter;
 165         }
 166 
 167         @Override
 168         public void run() {
 169             int allocatedObjects = 0;
 170             try {
 171                 while (isRunning && allocatedObjects < totalObjects) {
 172                     createObject();
 173                     allocatedObjects++;
 174                     allocationCounter.incrementAndGet();
 175                 }
 176 
 177             } catch (OutOfMemoryError oome) {
 178                 GARBAGE.clear();
 179                 System.out.print("OOME was caught.");
 180                 System.out.println(" Allocated in thread: " + allocatedObjects + " . Totally allocated: " + allocationCounter.get() + ".");
 181             } finally {
 182                 cdl.countDown();
 183             }
 184         }
 185     }
 186 
 187     /**
 188      * Simple Runnable which waits TIMEOUT and sets isRunning to false.
 189      */
 190     class Timer implements Runnable {
 191 
 192         @Override
 193         public void run() {
 194             try {
 195                 Thread.sleep(TIMEOUT * 1000);
 196             } catch (InterruptedException ex) {
 197             }
 198             setDone();
 199         }
 200     }
 201 }