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