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 
  57     private volatile boolean isRunning;
  58     private final ExecutorService threadExecutor;
  59     private final AtomicInteger alocatedObjectsCount;
  60     private CountDownLatch countDownLatch;
  61     public static final List<Object> GARBAGE = Collections.synchronizedList(new ArrayList<>());
  62 
  63     public static void main(String[] args) throws InterruptedException {
  64         new TestStressG1Humongous().run();
  65     }
  66 
  67     public TestStressG1Humongous() {
  68         isRunning = true;
  69         threadExecutor = Executors.newFixedThreadPool(THREAD_COUNT + 1);
  70         alocatedObjectsCount = new AtomicInteger(0);
  71     }
  72 
  73     private void run() throws InterruptedException {
  74         threadExecutor.submit(new Timer());
  75         int checkedAmountOfHObjects = getExpectedAmountOfObjects();
  76         while (isRunning()) {
  77             countDownLatch = new CountDownLatch(THREAD_COUNT);
  78             startAllocationThreads(checkedAmountOfHObjects);
  79             countDownLatch.await();
  80             GARBAGE.clear();
  81             System.out.println("Allocated " + alocatedObjectsCount.get() + " objects.");
  82             alocatedObjectsCount.set(0);
  83         }
  84         threadExecutor.shutdown();
  85         System.out.println("Done!");
  86     }
  87 
  88     /**
  89      * Tries to fill available memory with humongous objects to get expected amount.
  90      * @return expected amount of humongous objects
  91      */
  92     private int getExpectedAmountOfObjects() {
  93         long maxMem = Runtime.getRuntime().maxMemory();
  94         int expectedHObjects = (int) (maxMem / HUMONGOUS_SIZE);
  95         // Will allocate 2 region less to give some free space for VM.
  96         int checkedAmountOfHObjects = checkHeapCapacity(expectedHObjects) - 2;
  97         return checkedAmountOfHObjects;
  98     }
  99 
 100     /**
 101      * Starts several threads to allocate the requested amount of humongous objects.
 102      * @param totalObjects total amount of object that will be created
 103      */
 104     private void startAllocationThreads(int totalObjects) {
 105         int objectsPerThread = totalObjects / THREAD_COUNT;
 106         int objectsForLastThread = objectsPerThread + totalObjects % THREAD_COUNT;
 107         for (int i = 0; i < THREAD_COUNT - 1; ++i) {
 108             threadExecutor.submit(new AllocationThread(countDownLatch, objectsPerThread, alocatedObjectsCount));
 109         }
 110         threadExecutor.submit(new AllocationThread(countDownLatch, objectsForLastThread, alocatedObjectsCount));
 111     }
 112 
 113     /**
 114      * Creates a humongous object of the predefined size.
 115      */
 116     private void createObject() {
 117         GARBAGE.add(new byte[HUMONGOUS_SIZE]);
 118     }
 119 
 120     /**
 121      * Tries to create the requested amount of humongous objects.
 122      * In case of OOME, stops creating and cleans the created garbage.
 123      * @param expectedObjects amount of objects based on heap size
 124      * @return amount of created objects
 125      */
 126     private int checkHeapCapacity(int expectedObjects) {
 127         int allocated = 0;
 128         try {
 129             while (isRunning() && allocated < expectedObjects) {
 130                 createObject();
 131                 ++allocated;
 132             }
 133         } catch (OutOfMemoryError oome) {
 134             GARBAGE.clear();
 135         }
 136         return allocated;
 137     }
 138 
 139     private void setDone() {
 140         isRunning = false;
 141     }
 142 
 143     private boolean isRunning() {
 144         return isRunning;
 145     }
 146 
 147     /**
 148      * Thread which allocates requested amount of humongous objects.
 149      */
 150     private class AllocationThread implements Runnable {
 151 
 152         private final int totalObjects;
 153         private final CountDownLatch cdl;
 154         private final AtomicInteger allocationCounter;
 155 
 156         /**
 157          * Creates allocation thread
 158          * @param cdl CountDownLatch
 159          * @param objects amount of objects to allocate
 160          * @param counter
 161          */
 162         public AllocationThread(CountDownLatch cdl, int objects, AtomicInteger counter) {
 163             totalObjects = objects;
 164             this.cdl = cdl;
 165             allocationCounter = counter;
 166         }
 167 
 168         @Override
 169         public void run() {
 170             int allocatedObjects = 0;
 171             try {
 172                 while (isRunning && allocatedObjects < totalObjects) {
 173                     createObject();
 174                     allocatedObjects++;
 175                     allocationCounter.incrementAndGet();
 176                 }
 177 
 178             } catch (OutOfMemoryError oome) {
 179                 GARBAGE.clear();
 180                 System.out.print("OOME was caught.");
 181                 System.out.println(" Allocated in thread: " + allocatedObjects + " . Totally allocated: " + allocationCounter.get() + ".");
 182             } finally {
 183                 cdl.countDown();
 184             }
 185         }
 186     }
 187 
 188     /**
 189      * Simple Runnable which waits TIMEOUT and sets isRunning to false.
 190      */
 191     class Timer implements Runnable {
 192 
 193         @Override
 194         public void run() {
 195             try {
 196                 Thread.sleep(TIMEOUT * 1000);
 197             } catch (InterruptedException ex) {
 198             }
 199             setDone();
 200         }
 201     }
 202 }