1 /*
   2  * Copyright (c) 2015, 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 TestGreyReclaimedHumongousObjects.java
  26  * @bug 8069367
  27  * @requires vm.gc == "G1" | vm.gc == "null"
  28  * @summary Test handling of marked but unscanned reclaimed humongous objects.
  29  * @key gc
  30  * @run main/othervm -XX:+UseG1GC -Xss32m -Xmx128m -XX:G1HeapRegionSize=1m
  31  *      -XX:+UnlockExperimentalVMOptions
  32  *          -XX:+G1EagerReclaimHumongousObjects
  33  *          -XX:+G1EagerReclaimHumongousObjectsWithStaleRefs
  34  *      TestGreyReclaimedHumongousObjects 1048576 90
  35  */
  36 
  37 // This test spawns a bunch of threads, each of them rapidly
  38 // allocating large objects and storing them into a circular buffer
  39 // associated with the thread.  The circular buffer results in these
  40 // objects becoming dead in fairly short order.
  41 //
  42 // The situation we're trying to provoke is
  43 //
  44 // (1) A humongous object H is marked and added to the mark stack.
  45 //
  46 // (2) An evacuation pause determines H is no longer live, and
  47 // reclaims it.  This occurs before concurrent marking has gotten
  48 // around to processing the mark stack entry for H.
  49 //
  50 // (3) Concurrent marking processes the mark stack entry for H.  The
  51 // bug is that it would attempt to scan the now dead object.
  52 //
  53 // Unfortunately, this test is *very* sensitive to configuration.
  54 // Among the parameters that affect whether / how often we'll get into
  55 // the desired situation within a reasonable amount of time are:
  56 //
  57 // - THREAD_COUNT: The number of allocating threads.
  58 //
  59 // - OLD_COUNT: The number of objects each thread keeps.
  60 //
  61 // - MAX_MEMORY: The maximum heap size.
  62 //
  63 // - G1HeapRegionSize
  64 //
  65 // - The size of the objects being allocated.
  66 //
  67 // The parameter values specified here:
  68 //
  69 // - THREAD_COUNT = 12
  70 // - OLD_COUNT == 4
  71 // - MAX_MEMORY == 128m
  72 // - G1HeapRegionSize = 1m
  73 // - Object size = 1048576 (2 regions after header overhead and roundup)
  74 //
  75 // seems to work well at provoking the desired state fairly quickly.
  76 // Even relatively small perturbations may change that.  The key
  77 // factors seem to be keeping the heap mostly full of live objects but
  78 // having them become dead fairly quickly.
  79 
  80 import java.util.Date;
  81 import java.util.concurrent.ExecutorService;
  82 import java.util.concurrent.Executors;
  83 import java.util.concurrent.ThreadFactory;
  84 import java.util.concurrent.TimeUnit;
  85 import sun.management.ManagementFactoryHelper;
  86 import com.sun.management.HotSpotDiagnosticMXBean;
  87 import com.sun.management.VMOption;
  88 
  89 public class TestGreyReclaimedHumongousObjects {
  90 
  91     static class NamedThreadFactory implements ThreadFactory {
  92        private int threadNum = 0;
  93 
  94        @Override
  95        public Thread newThread(Runnable r) {
  96          return new Thread(r, THREAD_NAME + (threadNum++));
  97        }
  98     }
  99 
 100     static class Runner extends Thread {
 101         private final Date startDate = new Date();
 102         private final int obj_size;
 103         private final Object[] old_garbage;
 104         private int old_index = 0;
 105 
 106         public Runner(int obj_size) {
 107             this.obj_size = obj_size;
 108             old_garbage = new Object[OLD_COUNT];
 109         }
 110 
 111         private void allocate_garbage() {
 112             byte[] garbage = new byte[obj_size];
 113             old_garbage[Math.abs(++old_index % OLD_COUNT)] = garbage;
 114         }
 115 
 116         @Override
 117         public void run() {
 118             try {
 119                 while (!isInterrupted()) {
 120                     allocate_garbage();
 121                     Thread.sleep(0); // Yield, to ensure interruptable.
 122                 }
 123             } catch (InterruptedException e) {
 124                 System.out.println("Aborted after "
 125                                    + (new Date().getTime() - startDate.getTime())
 126                                    + " ms");
 127                 interrupt();
 128             }
 129         }
 130     }
 131 
 132     public static void main(String[] args) throws Exception {
 133         HotSpotDiagnosticMXBean diagnostic = ManagementFactoryHelper.getDiagnosticMXBean();
 134 
 135         System.out.println("Max memory= " + MAX_MEMORY + " bytes");
 136 
 137         int obj_size = 0;
 138         long seconds_to_run = 0;
 139         if (args.length != 2) {
 140             throw new RuntimeException("Object size argument must be supplied");
 141         } else {
 142             obj_size = Integer.parseInt(args[0]);
 143             seconds_to_run = Integer.parseInt(args[1]);
 144         }
 145         System.out.println("Objects size= " + obj_size + " bytes");
 146         System.out.println("Seconds to run=" + seconds_to_run);
 147 
 148         int region_size =
 149             Integer.parseInt(diagnostic.getVMOption("G1HeapRegionSize").getValue());
 150         if (obj_size < (region_size / 2)) {
 151             throw new RuntimeException("Object size " + obj_size +
 152                                        " is not humongous with region size " + region_size);
 153         }
 154 
 155         ExecutorService executor =
 156             Executors.newFixedThreadPool(THREAD_COUNT, new NamedThreadFactory());
 157         System.out.println("Starting " + THREAD_COUNT + " threads");
 158 
 159         for (int i = 0; i < THREAD_COUNT; i++) {
 160             executor.execute(new Runner(obj_size));
 161         }
 162 
 163         Thread.sleep(seconds_to_run * 1000);
 164         executor.shutdownNow();
 165 
 166         if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
 167             System.err.println("Thread pool did not terminate after 10 seconds after shutdown");
 168         }
 169     }
 170 
 171     private static final long MAX_MEMORY = Runtime.getRuntime().maxMemory();
 172     private static final int OLD_COUNT = 4;
 173     private static final int THREAD_COUNT = 12;
 174     private static final String THREAD_NAME = "TestGreyRH-";
 175 }
 176