1 /* 2 * Copyright (c) 2015, 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 package gc.testlibrary; 26 27 import jdk.test.lib.JDKToolLauncher; 28 import jdk.test.lib.OutputAnalyzer; 29 import sun.hotspot.WhiteBox; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.nio.file.Paths; 36 37 public class Helpers { 38 39 /** 40 * Size of a long field in bytes 41 */ 42 public static final int SIZE_OF_LONG = 8; 43 44 // In case of 128 byte padding 45 private static final int MAX_PADDING_SIZE = 128; 46 47 /** 48 * According class file format theoretical amount of fields in class is u2 which is (256 * 256 - 1). 49 * Some service info takes place in constant pool and we really could make a class with lesser amount of fields. 50 * 51 * Since the exact value is not so important and I would like to avoid issues that may be caused by future changes/ 52 * different archs etc I selected (256 * 256 - 1024) for this constant. 53 * The test works with other values too but the smaller the number the more classes we need to generate and it takes 54 * more time 55 */ 56 private static final int MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS = 256 * 256 - 1024; 57 58 /** 59 * Detects amount of extra bytes required to allocate a byte array. 60 * Allocating a byte[n] array takes more then just n bytes in the heap. 61 * Extra bytes are required to store object reference and the length. 62 * This amount depends on bitness and other factors. 63 * 64 * @return byte[] memory overhead 65 */ 66 public static int detectByteArrayAllocationOverhead() { 67 68 WhiteBox whiteBox = WhiteBox.getWhiteBox(); 69 70 int zeroLengthByteArraySize = (int) whiteBox.getObjectSize(new byte[0]); 71 72 // Since we do not know is there any padding in zeroLengthByteArraySize we cannot just take byte[0] size as overhead 73 for (int i = 1; i < MAX_PADDING_SIZE + 1; ++i) { 74 int realAllocationSize = (int) whiteBox.getObjectSize(new byte[i]); 75 if (realAllocationSize != zeroLengthByteArraySize) { 76 // It means we did not have any padding on previous step 77 return zeroLengthByteArraySize - (i - 1); 78 } 79 } 80 throw new Error("We cannot find byte[] memory overhead - should not reach here"); 81 } 82 83 /** 84 * Compiles a java class 85 * 86 * @param className class name 87 * @param root root directory - where .java and .class files will be put 88 * @param source class source 89 * @throws IOException if cannot write file to specified directory 90 */ 91 public static void compileClass(String className, Path root, String source) throws IOException { 92 Path sourceFile = root.resolve(className + ".java"); 93 Files.write(sourceFile, source.getBytes()); 94 95 JDKToolLauncher jar = JDKToolLauncher.create("javac") 96 .addToolArg("-d") 97 .addToolArg(root.toAbsolutePath().toString()) 98 .addToolArg("-cp") 99 .addToolArg(System.getProperty("java.class.path") + File.pathSeparator + root.toAbsolutePath()) 100 .addToolArg(sourceFile.toAbsolutePath().toString()); 101 102 ProcessBuilder pb = new ProcessBuilder(jar.getCommand()); 103 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 104 output.shouldHaveExitValue(0); 105 } 106 107 /** 108 * Generates class with specified name, which extends specified class, with specified constructor and specified 109 * count of long fields 110 * Generated class will looks like this: 111 * public class ClassName extends SuperClass { 112 * ClassName() {super();} 113 * long f0; 114 * ... 115 * long fNNN; 116 * <p> 117 * } 118 * 119 * @param className class name 120 * @param superClass super class. if null - no extends clause 121 * @param constructor constructor. if null - no constructor 122 * @param fieldCount count of long fields 123 * @return class text 124 */ 125 public static String generate(String className, String superClass, String constructor, long fieldCount) { 126 127 StringBuilder builder = new StringBuilder(); 128 builder.append(String.format("public class %s%s {\n", className, superClass == null ? "" 129 : " extends " + superClass)); 130 131 if (constructor != null) { 132 builder.append(constructor); 133 } 134 135 for (int i = 0; i < fieldCount; ++i) { 136 builder.append(String.format("long f%d;\n", i)); 137 } 138 139 builder.append("}\n"); 140 return builder.toString(); 141 } 142 143 /** 144 * Generates specified amount of long fields 145 * Result string will looks like this: 146 * <p> 147 * long f0; 148 * ... 149 * long fNNN; 150 * 151 * @param fieldCount count of long fields 152 * @return generated fields 153 */ 154 private static String fieldsGenerator(long fieldCount) { 155 StringBuilder fieldsBuilder = new StringBuilder(); 156 157 for (int i = 0; i < fieldCount; ++i) { 158 fieldsBuilder.append(String.format("long f%d;\n", i)); 159 } 160 161 return fieldsBuilder.toString(); 162 } 163 164 165 /** 166 * Changes string from enum notation to class notation - i.e. "VERY_SMALL_CAT" to "VerySmallCat" 167 * 168 * @param enumName string in enum notation 169 * @return string in class notation 170 */ 171 public static String enumNameToClassName(String enumName) { 172 if (enumName == null) { 173 return null; 174 } 175 176 StringBuilder builder = new StringBuilder(); 177 boolean toLowerCase = false; 178 for (int i = 0; i < enumName.length(); ++i) { 179 if (enumName.charAt(i) == '_') { 180 toLowerCase = false; 181 } else { 182 builder.append(toLowerCase ? String.valueOf(enumName.charAt(i)).toLowerCase() : 183 String.valueOf(enumName.charAt(i))); 184 toLowerCase = true; 185 } 186 187 } 188 return builder.toString(); 189 } 190 191 /** 192 * Generates and compiles class with instance of specified size and load it in specified class loader 193 * Generated class will looks like this: 194 * public class ClassName extends SuperClass { 195 * long f0; 196 * ... 197 * long fNNN; 198 * <p> 199 * } 200 * 201 * @param classLoader class loader 202 * @param className generated class name 203 * @param instanceSize size of generated class' instance. Size should be aligned by 8 bytes 204 * @param workDir working dir where generated classes are put and compiled 205 * @param prefix prefix for service classes (ones we use to create chain of inheritance). 206 * The names will be prefix_1, prefix_2,.., prefix_n 207 * @return Class object of generated and compiled class loaded in specified class loader 208 * @throws IOException 209 * @throws ClassNotFoundException 210 */ 211 public static Class<?> generateCompileAndLoad(ClassLoader classLoader, String className, long instanceSize, 212 Path workDir, String prefix) 213 throws IOException, ClassNotFoundException { 214 215 if (instanceSize % SIZE_OF_LONG != 0L) { 216 throw new Error(String.format("Test bug: only sizes aligned by 8 bytes are supported and %d was specified", 217 instanceSize)); 218 } 219 220 long instanceSizeWithoutObjectHeader = instanceSize - WhiteBox.getWhiteBox().getObjectSize(new Object()); 221 222 int generatedClassesCount; 223 int fieldsInLastClassCount; 224 225 int sizeOfLastFile = (int) (instanceSizeWithoutObjectHeader 226 % (MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS * SIZE_OF_LONG)); 227 228 if (sizeOfLastFile != 0) { 229 generatedClassesCount = (int) instanceSizeWithoutObjectHeader 230 / (MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS * SIZE_OF_LONG) + 1; 231 fieldsInLastClassCount = sizeOfLastFile / SIZE_OF_LONG; 232 } else { 233 generatedClassesCount = (int) instanceSizeWithoutObjectHeader 234 / (MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS * SIZE_OF_LONG); 235 fieldsInLastClassCount = MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS; 236 } 237 238 for (int i = 0; i < generatedClassesCount; i++) { 239 // for the last generated class we use specified class name 240 String clsName = (i == generatedClassesCount - 1) ? className : prefix + i; 241 242 // If we already have a file with the same name we do not create it again 243 if (Files.notExists(Paths.get(clsName + ".java"))) { 244 Helpers.compileClass(clsName, workDir, 245 Helpers.generate( 246 clsName, 247 // for first generated class we don't have 'extends' 248 (i == 0 ? null : prefix + (i - 1)), 249 null, 250 // for the last generated class we use different field count 251 (i == generatedClassesCount - 1) ? fieldsInLastClassCount 252 : MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS)); 253 } else { 254 System.out.println("Class " + clsName + 255 ".java already exists, skipping class' generation and compilation"); 256 } 257 258 } 259 return classLoader.loadClass(className); 260 } 261 262 /** 263 * Creates a class which instances will be approximately of the requested size. 264 * This method produces a java source from a class template by substituting values instead of parameters. 265 * Then the obtained source is compiled. 266 * Generated class will looks like this: 267 * classTemplate 268 * constructorTemplate 269 * long f0; 270 * ... 271 * long fNNN; 272 * <p> 273 * } 274 * 275 * @param className generated class name 276 * @param baseClass base class 277 * @param classTemplate class template - the first part of class. ${ClassName} and ${BaseClass} will be replaced 278 * with values from className and baseClass,one entry of ${Fields} will be replaced with 279 * generated long fields. Class template should look like this: 280 * imports; 281 * public class ${ClassName} extends ${BaseClass} { 282 * public ${ClassName} { some code here;} 283 * some methods 284 * ${Fields} 285 * 286 * } 287 * @param constructorTemplate constructor template, ${ClassName} would be replaced on actual class name 288 * @param instanceSize size of generated class' instance. Size should be aligned by 8 bytes 289 * @param workDir working dir where generated classes are put and compiled 290 * @param prefix prefix for service classes (ones we use to create chain of inheritance). 291 * The names will be prefix_1, prefix_2,.., prefix_n 292 * @return Class object of generated and compiled class loaded in specified class loader 293 * @throws IOException if cannot write or read to workDir 294 */ 295 public static void generateByTemplateAndCompile(String className, String baseClass, String classTemplate, 296 String constructorTemplate, long instanceSize, Path workDir, 297 String prefix) throws IOException { 298 299 if (instanceSize % SIZE_OF_LONG != 0L) { 300 throw new Error(String.format("Test bug: only sizes aligned by %d bytes are supported and %d was specified", 301 SIZE_OF_LONG, instanceSize)); 302 } 303 304 int instanceSizeWithoutObjectHeaderInWords = 305 (int) (instanceSize - WhiteBox.getWhiteBox().getObjectSize(new Object())) / SIZE_OF_LONG; 306 307 if (instanceSizeWithoutObjectHeaderInWords <= 0) { 308 throw new Error(String.format("Test bug: specified instance size is too small - %d." 309 + " Cannot generate any classes", instanceSize)); 310 } 311 312 int sizeOfLastFile = instanceSizeWithoutObjectHeaderInWords % MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS; 313 int generatedClassesCount = instanceSizeWithoutObjectHeaderInWords / MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS; 314 315 // Do all the classes have the maximum number of fields? 316 int fieldsInLastClassCount; 317 318 if (sizeOfLastFile == 0) { 319 fieldsInLastClassCount = MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS; 320 } else { 321 generatedClassesCount++; 322 fieldsInLastClassCount = sizeOfLastFile; 323 } 324 325 // first (generatedClassesCount - 1) classes are just fillers - just long fields and constructor 326 for (int i = 0; i < generatedClassesCount - 1; i++) { 327 String clsName = prefix + i; 328 329 Helpers.compileClass(clsName, workDir, 330 Helpers.generate( 331 clsName, 332 // first generated class extends base class 333 (i == 0 ? baseClass : prefix + (i - 1)), 334 constructorTemplate.replace("${ClassName}", clsName), 335 MAXIMUM_AMOUNT_OF_FIELDS_IN_CLASS)); 336 } 337 338 // generating last class - the one with specified className 339 Helpers.compileClass(className, workDir, 340 classTemplate.replaceAll("\\$\\{ClassName\\}", className) 341 // if no fillers were generated (generatedClassesCount == 1) 342 // the last class should extends baseClass 343 // otherwise it should extend last generated filler class which name is 344 // prefix + (generatedClassesCount - 2) 345 // generatedClassesCount is always not smaller than 1 346 .replace("${BaseClass}", 347 generatedClassesCount == 1 ? baseClass : 348 prefix + (generatedClassesCount - 2)) 349 .replace("${Fields}", fieldsGenerator(fieldsInLastClassCount)) 350 ); 351 } 352 353 /** 354 * Waits until Concurent Mark Cycle finishes 355 * 356 * @param wb Whitebox instance 357 * @param sleepTime sleep time 358 */ 359 public static void waitTillCMCFinished(WhiteBox wb, int sleepTime) { 360 while (wb.g1InConcurrentMark()) { 361 if (sleepTime > -1) { 362 try { 363 Thread.sleep(sleepTime); 364 } catch (InterruptedException e) { 365 System.out.println("Got InterruptedException while waiting for ConcMarkCycle to finish"); 366 Thread.currentThread().interrupt(); 367 break; 368 } 369 } 370 } 371 } 372 373 }