1 /*
   2  * Copyright (c) 2014, 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 import java.lang.management.ManagementFactory;
  25 import java.lang.management.MemoryPoolMXBean;
  26 import java.util.Objects;
  27 import java.util.Optional;
  28 import java.util.regex.Matcher;
  29 import java.util.regex.Pattern;
  30 
  31 import jdk.test.lib.Asserts;
  32 import com.sun.management.ThreadMXBean;
  33 import sun.hotspot.WhiteBox;
  34 import jdk.internal.misc.Unsafe;
  35 
  36 /**
  37  * Main class for tests on {@code SurvivorAlignmentInBytes} option.
  38  *
  39  * Typical usage is to obtain instance using fromArgs method, allocate objects
  40  * and verify that actual memory usage in tested heap space is close to
  41  * expected.
  42  */
  43 public class SurvivorAlignmentTestMain {
  44     enum HeapSpace {
  45         EDEN,
  46         SURVIVOR,
  47         TENURED
  48     }
  49 
  50     public static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
  51 
  52     public static final long MAX_TENURING_THRESHOLD = Optional.ofNullable(
  53             SurvivorAlignmentTestMain.WHITE_BOX.getIntxVMFlag(
  54                     "MaxTenuringThreshold")).orElse(15L);
  55 
  56     /**
  57      * Regexp used to parse memory size params, like 2G, 34m or 15k.
  58      */
  59     private static final Pattern SIZE_REGEX
  60             = Pattern.compile("(?<size>[0-9]+)(?<multiplier>[GMKgmk])?");
  61 
  62     // Names of different heap spaces.
  63     private static final String DEF_NEW_EDEN = "Eden Space";
  64     private static final String DEF_NEW_SURVIVOR = "Survivor Space";
  65     private static final String PAR_NEW_EDEN = "Par Eden Space";
  66     private static final String PAR_NEW_SURVIVOR = "Par Survivor Space";
  67     private static final String PS_EDEN = "PS Eden Space";
  68     private static final String PS_SURVIVOR = "PS Survivor Space";
  69     private static final String G1_EDEN = "G1 Eden Space";
  70     private static final String G1_SURVIVOR = "G1 Survivor Space";
  71     private static final String SERIAL_TENURED = "Tenured Gen";
  72     private static final String CMS_TENURED = "CMS Old Gen";
  73     private static final String PS_TENURED = "PS Old Gen";
  74     private static final String G1_TENURED = "G1 Old Gen";
  75 
  76     private static final long G1_HEAP_REGION_SIZE = Optional.ofNullable(
  77             SurvivorAlignmentTestMain.WHITE_BOX.getUintxVMFlag(
  78                     "G1HeapRegionSize")).orElse(-1L);
  79 
  80     /**
  81      * Min size of free chunk in CMS generation.
  82      * An object allocated in CMS generation will at least occupy this amount
  83      * of bytes.
  84      */
  85     private static final long CMS_MIN_FREE_CHUNK_SIZE
  86             = 3L * Unsafe.ADDRESS_SIZE;
  87 
  88     private static final AlignmentHelper EDEN_SPACE_HELPER;
  89     private static final AlignmentHelper SURVIVOR_SPACE_HELPER;
  90     private static final AlignmentHelper TENURED_SPACE_HELPER;
  91     /**
  92      * Amount of memory that should be filled during a test run.
  93      */
  94     private final long memoryToFill;
  95     /**
  96      * The size of an objects that will be allocated during a test run.
  97      */
  98     private final long objectSize;
  99     /**
 100      * Amount of memory that will be actually occupied by an object in eden
 101      * space.
 102      */
 103     private final long actualObjectSize;
 104     /**
 105      * Storage for allocated objects.
 106      */
 107     private final Object[] garbage;
 108     /**
 109      * Heap space whose memory usage is a subject of assertions during the test
 110      * run.
 111      */
 112     private final HeapSpace testedSpace;
 113 
 114     private long[] baselinedThreadMemoryUsage = null;
 115     private long[] threadIds = null;
 116 
 117     /**
 118      * Initialize {@code EDEN_SPACE_HELPER}, {@code SURVIVOR_SPACE_HELPER} and
 119      * {@code TENURED_SPACE_HELPER} to represent heap spaces in use.
 120      *
 121      * Note that regardless to GC object's alignment in survivor space is
 122      * expected to be equal to {@code SurvivorAlignmentInBytes} value and
 123      * alignment in other spaces is expected to be equal to
 124      * {@code ObjectAlignmentInBytes} value.
 125      *
 126      * In CMS generation we can't allocate less then {@code MinFreeChunk} value,
 127      * for other CGs we expect that object of size {@code MIN_OBJECT_SIZE}
 128      * could be allocated as it is (of course, its size could be aligned
 129      * according to alignment value used in a particular space).
 130      *
 131      * For G1 GC MXBeans could report memory usage only with region size
 132      * precision (if an object allocated in some G1 heap region, then all region
 133      * will claimed as used), so for G1's spaces precision is equal to
 134      * {@code G1HeapRegionSize} value.
 135      */
 136     static {
 137         AlignmentHelper edenHelper = null;
 138         AlignmentHelper survivorHelper = null;
 139         AlignmentHelper tenuredHelper = null;
 140         for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
 141             switch (pool.getName()) {
 142                 case SurvivorAlignmentTestMain.DEF_NEW_EDEN:
 143                 case SurvivorAlignmentTestMain.PAR_NEW_EDEN:
 144                 case SurvivorAlignmentTestMain.PS_EDEN:
 145                     Asserts.assertNull(edenHelper,
 146                             "Only one bean for eden space is expected.");
 147                     edenHelper = new AlignmentHelper(
 148                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 149                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 150                             AlignmentHelper.MIN_OBJECT_SIZE, pool);
 151                     break;
 152                 case SurvivorAlignmentTestMain.G1_EDEN:
 153                     Asserts.assertNull(edenHelper,
 154                             "Only one bean for eden space is expected.");
 155                     edenHelper = new AlignmentHelper(
 156                             SurvivorAlignmentTestMain.G1_HEAP_REGION_SIZE,
 157                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 158                             AlignmentHelper.MIN_OBJECT_SIZE, pool);
 159                     break;
 160                 case SurvivorAlignmentTestMain.DEF_NEW_SURVIVOR:
 161                 case SurvivorAlignmentTestMain.PAR_NEW_SURVIVOR:
 162                 case SurvivorAlignmentTestMain.PS_SURVIVOR:
 163                     Asserts.assertNull(survivorHelper,
 164                             "Only one bean for survivor space is expected.");
 165                     survivorHelper = new AlignmentHelper(
 166                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 167                             AlignmentHelper.SURVIVOR_ALIGNMENT_IN_BYTES,
 168                             AlignmentHelper.MIN_OBJECT_SIZE, pool);
 169                     break;
 170                 case SurvivorAlignmentTestMain.G1_SURVIVOR:
 171                     Asserts.assertNull(survivorHelper,
 172                             "Only one bean for survivor space is expected.");
 173                     survivorHelper = new AlignmentHelper(
 174                             SurvivorAlignmentTestMain.G1_HEAP_REGION_SIZE,
 175                             AlignmentHelper.SURVIVOR_ALIGNMENT_IN_BYTES,
 176                             AlignmentHelper.MIN_OBJECT_SIZE, pool);
 177                     break;
 178                 case SurvivorAlignmentTestMain.SERIAL_TENURED:
 179                 case SurvivorAlignmentTestMain.PS_TENURED:
 180                 case SurvivorAlignmentTestMain.G1_TENURED:
 181                     Asserts.assertNull(tenuredHelper,
 182                             "Only one bean for tenured space is expected.");
 183                     tenuredHelper = new AlignmentHelper(
 184                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 185                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 186                             AlignmentHelper.MIN_OBJECT_SIZE, pool);
 187                     break;
 188                 case SurvivorAlignmentTestMain.CMS_TENURED:
 189                     Asserts.assertNull(tenuredHelper,
 190                             "Only one bean for tenured space is expected.");
 191                     tenuredHelper = new AlignmentHelper(
 192                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 193                             AlignmentHelper.OBJECT_ALIGNMENT_IN_BYTES,
 194                             SurvivorAlignmentTestMain.CMS_MIN_FREE_CHUNK_SIZE,
 195                             pool);
 196                     break;
 197             }
 198         }
 199         EDEN_SPACE_HELPER = Objects.requireNonNull(edenHelper,
 200                 "AlignmentHelper for eden space should be initialized.");
 201         SURVIVOR_SPACE_HELPER = Objects.requireNonNull(survivorHelper,
 202                 "AlignmentHelper for survivor space should be initialized.");
 203         TENURED_SPACE_HELPER = Objects.requireNonNull(tenuredHelper,
 204                 "AlignmentHelper for tenured space should be initialized.");
 205     }
 206     /**
 207      * Returns an SurvivorAlignmentTestMain instance constructed using CLI
 208      * options.
 209      *
 210      * Following options are expected:
 211      * <ul>
 212      *     <li>memoryToFill</li>
 213      *     <li>objectSize</li>
 214      * </ul>
 215      *
 216      * Both argument may contain multiplier suffix k, m or g.
 217      */
 218     public static SurvivorAlignmentTestMain fromArgs(String[] args) {
 219         Asserts.assertEQ(args.length, 3, "Expected three arguments: "
 220                 + "memory size, object size and tested heap space name.");
 221 
 222         long memoryToFill = parseSize(args[0]);
 223         long objectSize = Math.max(parseSize(args[1]),
 224                 AlignmentHelper.MIN_ARRAY_SIZE);
 225         HeapSpace testedSpace = HeapSpace.valueOf(args[2]);
 226 
 227         return new SurvivorAlignmentTestMain(memoryToFill, objectSize,
 228                 testedSpace);
 229     }
 230 
 231     /**
 232      * Returns a value parsed from a string with format
 233      * &lt;integer&gt;&lt;multiplier&gt;.
 234      */
 235     private static long parseSize(String sizeString) {
 236         Matcher matcher = SIZE_REGEX.matcher(sizeString);
 237         Asserts.assertTrue(matcher.matches(),
 238                 "sizeString should have following format \"[0-9]+([MBK])?\"");
 239         long size = Long.valueOf(matcher.group("size"));
 240 
 241         if (matcher.group("multiplier") != null) {
 242             long K = 1024L;
 243             // fall through multipliers
 244             switch (matcher.group("multiplier").toLowerCase()) {
 245                 case "g":
 246                     size *= K;
 247                 case "m":
 248                     size *= K;
 249                 case "k":
 250                     size *= K;
 251             }
 252         }
 253         return size;
 254     }
 255 
 256     private SurvivorAlignmentTestMain(long memoryToFill, long objectSize,
 257             HeapSpace testedSpace) {
 258         this.objectSize = objectSize;
 259         this.memoryToFill = memoryToFill;
 260         this.testedSpace = testedSpace;
 261 
 262         AlignmentHelper helper = SurvivorAlignmentTestMain.EDEN_SPACE_HELPER;
 263 
 264         this.actualObjectSize = helper.getObjectSizeInThisSpace(
 265                 this.objectSize);
 266         int arrayLength = helper.getObjectsCount(memoryToFill, this.objectSize);
 267         garbage = new Object[arrayLength];
 268     }
 269 
 270     /**
 271      * Allocate byte arrays to fill {@code memoryToFill} memory.
 272      */
 273     public void allocate() {
 274         int byteArrayLength = Math.max((int) (objectSize
 275                 - Unsafe.ARRAY_BYTE_BASE_OFFSET), 0);
 276 
 277         for (int i = 0; i < garbage.length; i++) {
 278             garbage[i] = new byte[byteArrayLength];
 279         }
 280     }
 281 
 282     /**
 283      * Release memory occupied after {@code allocate} call.
 284      */
 285     public void release() {
 286         for (int i = 0; i < garbage.length; i++) {
 287             garbage[i] = null;
 288         }
 289     }
 290 
 291     /**
 292      * Returns expected amount of memory occupied in a {@code heapSpace} by
 293      * objects referenced from {@code garbage} array.
 294      */
 295     public long getExpectedMemoryUsage() {
 296         AlignmentHelper alignmentHelper = getAlignmentHelper(testedSpace);
 297         return alignmentHelper.getExpectedMemoryUsage(objectSize,
 298                 garbage.length);
 299     }
 300 
 301     /**
 302      * Verifies that memory usage in a {@code heapSpace} deviates from
 303      * {@code expectedUsage} for no more than {@code MAX_RELATIVE_DEVIATION}.
 304      */
 305     public void verifyMemoryUsage(long expectedUsage) {
 306         AlignmentHelper alignmentHelper = getAlignmentHelper(testedSpace);
 307 
 308         long actualMemoryUsage = alignmentHelper.getActualMemoryUsage();
 309         boolean otherThreadsAllocatedMemory = areOtherThreadsAllocatedMemory();
 310 
 311         long memoryUsageDiff = Math.abs(actualMemoryUsage - expectedUsage);
 312         long maxAllowedUsageDiff
 313                 = alignmentHelper.getAllowedMemoryUsageDeviation(expectedUsage);
 314 
 315         System.out.println("Verifying memory usage in space: " + testedSpace);
 316         System.out.println("Allocated objects count: " + garbage.length);
 317         System.out.println("Desired object size: " + objectSize);
 318         System.out.println("Actual object size: " + actualObjectSize);
 319         System.out.println("Expected object size in space: "
 320                 + alignmentHelper.getObjectSizeInThisSpace(objectSize));
 321         System.out.println("Expected memory usage: " + expectedUsage);
 322         System.out.println("Actual memory usage: " + actualMemoryUsage);
 323         System.out.println("Memory usage diff: " + memoryUsageDiff);
 324         System.out.println("Max allowed usage diff: " + maxAllowedUsageDiff);
 325 
 326         if (memoryUsageDiff > maxAllowedUsageDiff
 327                 && otherThreadsAllocatedMemory) {
 328             System.out.println("Memory usage diff is incorrect, but it seems "
 329                     + "like someone else allocated objects");
 330             return;
 331         }
 332 
 333         Asserts.assertLTE(memoryUsageDiff, maxAllowedUsageDiff,
 334                 "Actual memory usage should not deviate from expected for " +
 335                         "more then " + maxAllowedUsageDiff);
 336     }
 337 
 338     /**
 339      * Baselines amount of memory allocated by each thread.
 340      */
 341     public void baselineMemoryAllocation() {
 342         ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
 343         threadIds = bean.getAllThreadIds();
 344         baselinedThreadMemoryUsage = bean.getThreadAllocatedBytes(threadIds);
 345     }
 346 
 347     /**
 348      * Checks if threads other then the current thread were allocating objects
 349      * after baselinedThreadMemoryUsage call.
 350      *
 351      * If baselinedThreadMemoryUsage was not called, then this method will return
 352      * {@code false}.
 353      */
 354     public boolean areOtherThreadsAllocatedMemory() {
 355         if (baselinedThreadMemoryUsage == null) {
 356             return false;
 357         }
 358 
 359         ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
 360         long currentMemoryAllocation[]
 361                 = bean.getThreadAllocatedBytes(threadIds);
 362         boolean otherThreadsAllocatedMemory = false;
 363 
 364         System.out.println("Verifying amount of memory allocated by threads:");
 365         for (int i = 0; i < threadIds.length; i++) {
 366             System.out.format("Thread %d%nbaseline allocation: %d"
 367                             + "%ncurrent allocation:%d%n", threadIds[i],
 368                     baselinedThreadMemoryUsage[i], currentMemoryAllocation[i]);
 369             System.out.println(bean.getThreadInfo(threadIds[i]));
 370 
 371             long bytesAllocated = Math.abs(currentMemoryAllocation[i]
 372                     - baselinedThreadMemoryUsage[i]);
 373             if (bytesAllocated > 0
 374                     && threadIds[i] != Thread.currentThread().getId()) {
 375                 otherThreadsAllocatedMemory = true;
 376             }
 377         }
 378 
 379         return otherThreadsAllocatedMemory;
 380     }
 381 
 382     @Override
 383     public String toString() {
 384         StringBuilder builder = new StringBuilder();
 385 
 386         builder.append(String.format("SurvivorAlignmentTestMain info:%n"));
 387         builder.append(String.format("Desired object size: %d%n", objectSize));
 388         builder.append(String.format("Memory to fill: %d%n", memoryToFill));
 389         builder.append(String.format("Objects to be allocated: %d%n",
 390                 garbage.length));
 391 
 392         builder.append(String.format("Alignment helpers to be used: %n"));
 393         for (HeapSpace heapSpace: HeapSpace.values()) {
 394             builder.append(String.format("For space %s:%n%s%n", heapSpace,
 395                     getAlignmentHelper(heapSpace)));
 396         }
 397 
 398         return builder.toString();
 399     }
 400 
 401     /**
 402      * Returns {@code AlignmentHelper} for a space {@code heapSpace}.
 403      */
 404     public static AlignmentHelper getAlignmentHelper(HeapSpace heapSpace) {
 405         switch (heapSpace) {
 406             case EDEN:
 407                 return SurvivorAlignmentTestMain.EDEN_SPACE_HELPER;
 408             case SURVIVOR:
 409                 return SurvivorAlignmentTestMain.SURVIVOR_SPACE_HELPER;
 410             case TENURED:
 411                 return SurvivorAlignmentTestMain.TENURED_SPACE_HELPER;
 412             default:
 413                 throw new Error("Unexpected heap space: " + heapSpace);
 414         }
 415     }
 416 }