1 /*
   2  * Copyright (c) 2017, 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 // Stress the GC locker by calling GetPrimitiveArrayCritical while
  26 // concurrently filling up old gen.
  27 
  28 import java.lang.management.MemoryPoolMXBean;
  29 import java.lang.management.ManagementFactory;
  30 import java.lang.management.MemoryUsage;
  31 import java.nio.ByteBuffer;
  32 import java.util.ArrayDeque;
  33 import java.util.HashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Queue;
  37 
  38 final class ThreadUtils {
  39     public static void sleep(long durationMS) {
  40         try {
  41             Thread.sleep(durationMS);
  42         } catch (Exception e) {
  43         }
  44     }
  45 }
  46 
  47 class Filler {
  48     private static final int SIZE = 250000;
  49 
  50     private int[] i1 = new int[SIZE];
  51     private int[] i2 = new int[SIZE];
  52     private short[] s1 = new short[SIZE];
  53     private short[] s2 = new short[SIZE];
  54 
  55     private Map<Object, Object> map = new HashMap<>();
  56 
  57     public Filler() {
  58         for (int i = 0; i < 10000; i++) {
  59             map.put(new Object(), new Object());
  60         }
  61     }
  62 }
  63 
  64 class Exitable {
  65     private volatile boolean shouldExit = false;
  66 
  67     protected boolean shouldExit() {
  68         return shouldExit;
  69     }
  70 
  71     public void exit() {
  72         shouldExit = true;
  73     }
  74 }
  75 
  76 class MemoryWatcher {
  77     private MemoryPoolMXBean bean;
  78     private final int thresholdPromille = 750;
  79     private final int criticalThresholdPromille = 800;
  80     private final int minGCWaitMS = 1000;
  81     private final int minFreeWaitElapsedMS = 30000;
  82     private final int minFreeCriticalWaitMS = 500;
  83 
  84     private int lastUsage = 0;
  85     private long lastGCDetected = System.currentTimeMillis();
  86     private long lastFree = System.currentTimeMillis();
  87 
  88     public MemoryWatcher(String mxBeanName) {
  89         List<MemoryPoolMXBean> memoryBeans = ManagementFactory.getMemoryPoolMXBeans();
  90         for (MemoryPoolMXBean bean : memoryBeans) {
  91             if (bean.getName().equals(mxBeanName)) {
  92                 this.bean = bean;
  93                 break;
  94             }
  95         }
  96     }
  97 
  98     private int getMemoryUsage() {
  99         if (bean == null) {
 100             Runtime r = Runtime.getRuntime();
 101             float free = (float) r.freeMemory() / r.maxMemory();
 102             return Math.round((1 - free) * 1000);
 103         } else {
 104             MemoryUsage usage = bean.getUsage();
 105             float used = (float) usage.getUsed() / usage.getCommitted();
 106             return Math.round(used * 1000);
 107         }
 108     }
 109 
 110     public synchronized boolean shouldFreeUpSpace() {
 111         int usage = getMemoryUsage();
 112         long now = System.currentTimeMillis();
 113 
 114         boolean detectedGC = false;
 115         if (usage < lastUsage) {
 116             lastGCDetected = now;
 117             detectedGC = true;
 118         }
 119 
 120         lastUsage = usage;
 121 
 122         long elapsed = now - lastFree;
 123         long timeSinceLastGC = now - lastGCDetected;
 124 
 125         if (usage > criticalThresholdPromille && elapsed > minFreeCriticalWaitMS) {
 126             lastFree = now;
 127             return true;
 128         } else if (usage > thresholdPromille && !detectedGC) {
 129             if (elapsed > minFreeWaitElapsedMS || timeSinceLastGC > minGCWaitMS) {
 130                 lastFree = now;
 131                 return true;
 132             }
 133         }
 134 
 135         return false;
 136     }
 137 }
 138 
 139 class MemoryUser extends Exitable implements Runnable {
 140     private final Queue<Filler> cache = new ArrayDeque<Filler>();
 141     private final MemoryWatcher watcher;
 142 
 143     private void load() {
 144         if (watcher.shouldFreeUpSpace()) {
 145             int toRemove = cache.size() / 5;
 146             for (int i = 0; i < toRemove; i++) {
 147                 cache.remove();
 148             }
 149         }
 150         cache.add(new Filler());
 151     }
 152 
 153     public MemoryUser(String mxBeanName) {
 154         watcher = new MemoryWatcher(mxBeanName);
 155     }
 156 
 157     @Override
 158     public void run() {
 159         for (int i = 0; i < 200; i++) {
 160             load();
 161         }
 162 
 163         while (!shouldExit()) {
 164             load();
 165         }
 166     }
 167 }
 168 
 169 class GCLockerStresser extends Exitable implements Runnable {
 170     static native void fillWithRandomValues(byte[] array);
 171 
 172     @Override
 173     public void run() {
 174         byte[] array = new byte[1024 * 1024];
 175         while (!shouldExit()) {
 176             fillWithRandomValues(array);
 177         }
 178     }
 179 }
 180 
 181 public class TestGCLocker {
 182     private static Exitable startGCLockerStresser(String name) {
 183         GCLockerStresser task = new GCLockerStresser();
 184 
 185         Thread thread = new Thread(task);
 186         thread.setName(name);
 187         thread.setPriority(Thread.MIN_PRIORITY);
 188         thread.start();
 189 
 190         return task;
 191     }
 192 
 193     private static Exitable startMemoryUser(String mxBeanName) {
 194         MemoryUser task = new MemoryUser(mxBeanName);
 195 
 196         Thread thread = new Thread(task);
 197         thread.setName("Memory User");
 198         thread.start();
 199 
 200         return task;
 201     }
 202 
 203     public static void main(String[] args) {
 204         System.loadLibrary("TestGCLocker");
 205 
 206         long durationMinutes = args.length > 0 ? Long.parseLong(args[0]) : 5;
 207         String mxBeanName = args.length > 1 ? args[1] : null;
 208 
 209         long startMS = System.currentTimeMillis();
 210 
 211         Exitable stresser1 = startGCLockerStresser("GCLockerStresser1");
 212         Exitable stresser2 = startGCLockerStresser("GCLockerStresser2");
 213         Exitable memoryUser = startMemoryUser(mxBeanName);
 214 
 215         long durationMS = durationMinutes * 60 * 1000;
 216         while ((System.currentTimeMillis() - startMS) < durationMS) {
 217             ThreadUtils.sleep(10 * 1010);
 218         }
 219 
 220         stresser1.exit();
 221         stresser2.exit();
 222         memoryUser.exit();
 223     }
 224 }