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 /* 25 * Common code for string deduplication tests 26 */ 27 28 import java.lang.management.*; 29 import java.lang.reflect.*; 30 import java.security.*; 31 import java.util.*; 32 import jdk.test.lib.*; 33 import sun.misc.*; 34 35 class TestStringDeduplicationTools { 36 private static final String YoungGC = "YoungGC"; 37 private static final String FullGC = "FullGC"; 38 39 private static final int Xmn = 50; // MB 40 private static final int Xms = 100; // MB 41 private static final int Xmx = 100; // MB 42 private static final int MB = 1024 * 1024; 43 private static final int StringLength = 50; 44 45 private static Field valueField; 46 private static Unsafe unsafe; 47 private static byte[] dummy; 48 49 static { 50 try { 51 Field field = Unsafe.class.getDeclaredField("theUnsafe"); 52 field.setAccessible(true); 53 unsafe = (Unsafe)field.get(null); 54 55 valueField = String.class.getDeclaredField("value"); 56 valueField.setAccessible(true); 57 } catch (Exception e) { 58 throw new RuntimeException(e); 59 } 60 } 61 62 private static Object getValue(String string) { 63 try { 64 return valueField.get(string); 65 } catch (Exception e) { 66 throw new RuntimeException(e); 67 } 68 } 69 70 private static void doFullGc(int numberOfTimes) { 71 for (int i = 0; i < numberOfTimes; i++) { 72 System.out.println("Begin: Full GC " + (i + 1) + "/" + numberOfTimes); 73 System.gc(); 74 System.out.println("End: Full GC " + (i + 1) + "/" + numberOfTimes); 75 } 76 } 77 78 private static void doYoungGc(int numberOfTimes) { 79 // Provoke at least numberOfTimes young GCs 80 final int objectSize = 128; 81 final int maxObjectInYoung = (Xmn * MB) / objectSize; 82 for (int i = 0; i < numberOfTimes; i++) { 83 System.out.println("Begin: Young GC " + (i + 1) + "/" + numberOfTimes); 84 for (int j = 0; j < maxObjectInYoung + 1; j++) { 85 dummy = new byte[objectSize]; 86 } 87 System.out.println("End: Young GC " + (i + 1) + "/" + numberOfTimes); 88 } 89 } 90 91 private static void forceDeduplication(int ageThreshold, String gcType) { 92 // Force deduplication to happen by either causing a FullGC or a YoungGC. 93 // We do several collections to also provoke a situation where the the 94 // deduplication thread needs to yield while processing the queue. This 95 // also tests that the references in the deduplication queue are adjusted 96 // accordingly. 97 if (gcType.equals(FullGC)) { 98 doFullGc(3); 99 } else { 100 doYoungGc(ageThreshold + 3); 101 } 102 } 103 104 private static String generateString(int id) { 105 StringBuilder builder = new StringBuilder(StringLength); 106 107 builder.append("DeduplicationTestString:" + id + ":"); 108 109 while (builder.length() < StringLength) { 110 builder.append('X'); 111 } 112 113 return builder.toString(); 114 } 115 116 private static ArrayList<String> createStrings(int total, int unique) { 117 System.out.println("Creating strings: total=" + total + ", unique=" + unique); 118 if (total % unique != 0) { 119 throw new RuntimeException("Total must be divisible by unique"); 120 } 121 122 ArrayList<String> list = new ArrayList<String>(total); 123 for (int j = 0; j < total / unique; j++) { 124 for (int i = 0; i < unique; i++) { 125 list.add(generateString(i)); 126 } 127 } 128 129 return list; 130 } 131 132 /** 133 * Verifies that the given list contains expected number of unique strings. 134 * It's possible that deduplication hasn't completed yet, so the method 135 * will perform several attempts to check with a little pause between. 136 * The method throws RuntimeException to signal that verification failed. 137 * 138 * @param list strings to check 139 * @param uniqueExpected expected number of unique strings 140 * @throws RuntimeException if check fails 141 */ 142 private static void verifyStrings(ArrayList<String> list, int uniqueExpected) { 143 boolean passed = false; 144 for (int attempts = 0; attempts < 10; attempts++) { 145 // Check number of deduplicated strings 146 ArrayList<Object> unique = new ArrayList<Object>(uniqueExpected); 147 for (String string: list) { 148 Object value = getValue(string); 149 boolean uniqueValue = true; 150 for (Object obj: unique) { 151 if (obj == value) { 152 uniqueValue = false; 153 break; 154 } 155 } 156 157 if (uniqueValue) { 158 unique.add(value); 159 } 160 } 161 162 System.out.println("Verifying strings: total=" + list.size() + 163 ", uniqueFound=" + unique.size() + 164 ", uniqueExpected=" + uniqueExpected); 165 166 if (unique.size() == uniqueExpected) { 167 System.out.println("Deduplication completed (as fast as " + attempts + " iterations)"); 168 passed = true; 169 break; 170 } else { 171 System.out.println("Deduplication not completed, waiting..."); 172 // Give the deduplication thread time to complete 173 try { 174 Thread.sleep(1000); 175 } catch (Exception e) { 176 throw new RuntimeException(e); 177 } 178 } 179 } 180 if (!passed) { 181 throw new RuntimeException("String verification failed"); 182 } 183 } 184 185 private static OutputAnalyzer runTest(String... extraArgs) throws Exception { 186 String[] defaultArgs = new String[] { 187 "-Xmn" + Xmn + "m", 188 "-Xms" + Xms + "m", 189 "-Xmx" + Xmx + "m", 190 "-XX:+UseG1GC", 191 "-XX:+UnlockDiagnosticVMOptions", 192 "-XX:+VerifyAfterGC" // Always verify after GC 193 }; 194 195 ArrayList<String> args = new ArrayList<String>(); 196 args.addAll(Arrays.asList(defaultArgs)); 197 args.addAll(Arrays.asList(extraArgs)); 198 199 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(args.toArray(new String[args.size()])); 200 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 201 System.err.println(output.getStderr()); 202 System.out.println(output.getStdout()); 203 return output; 204 } 205 206 private static class DeduplicationTest { 207 public static void main(String[] args) { 208 System.out.println("Begin: DeduplicationTest"); 209 210 final int numberOfStrings = Integer.parseUnsignedInt(args[0]); 211 final int numberOfUniqueStrings = Integer.parseUnsignedInt(args[1]); 212 final int ageThreshold = Integer.parseUnsignedInt(args[2]); 213 final String gcType = args[3]; 214 215 ArrayList<String> list = createStrings(numberOfStrings, numberOfUniqueStrings); 216 forceDeduplication(ageThreshold, gcType); 217 verifyStrings(list, numberOfUniqueStrings); 218 219 System.out.println("End: DeduplicationTest"); 220 } 221 222 public static OutputAnalyzer run(int numberOfStrings, int ageThreshold, String gcType, String... extraArgs) throws Exception { 223 String[] defaultArgs = new String[] { 224 "-XX:+UseStringDeduplication", 225 "-XX:StringDeduplicationAgeThreshold=" + ageThreshold, 226 DeduplicationTest.class.getName(), 227 "" + numberOfStrings, 228 "" + numberOfStrings / 2, 229 "" + ageThreshold, 230 gcType 231 }; 232 233 ArrayList<String> args = new ArrayList<String>(); 234 args.addAll(Arrays.asList(extraArgs)); 235 args.addAll(Arrays.asList(defaultArgs)); 236 237 return runTest(args.toArray(new String[args.size()])); 238 } 239 } 240 241 private static class InternedTest { 242 public static void main(String[] args) { 243 // This test verifies that interned strings are always 244 // deduplicated when being interned, and never after 245 // being interned. 246 247 System.out.println("Begin: InternedTest"); 248 249 final int ageThreshold = Integer.parseUnsignedInt(args[0]); 250 final String baseString = "DeduplicationTestString:" + InternedTest.class.getName(); 251 252 // Create duplicate of baseString 253 StringBuilder sb1 = new StringBuilder(baseString); 254 String dupString1 = sb1.toString(); 255 if (getValue(dupString1) == getValue(baseString)) { 256 throw new RuntimeException("Values should not match"); 257 } 258 259 // Force baseString to be inspected for deduplication 260 // and be inserted into the deduplication hashtable. 261 forceDeduplication(ageThreshold, FullGC); 262 263 // Wait for deduplication to occur 264 for (int attempts = 0; attempts < 10; attempts++) { 265 if (getValue(dupString1) == getValue(baseString)) { 266 break; 267 } 268 System.out.println("Waiting..."); 269 try { 270 Thread.sleep(1000); 271 } catch (Exception e) { 272 throw new RuntimeException(e); 273 } 274 } 275 if (getValue(dupString1) != getValue(baseString)) { 276 throw new RuntimeException("Deduplication has not occurred"); 277 } 278 279 // Create a new duplicate of baseString 280 StringBuilder sb2 = new StringBuilder(baseString); 281 String dupString2 = sb2.toString(); 282 if (getValue(dupString2) == getValue(baseString)) { 283 throw new RuntimeException("Values should not match"); 284 } 285 286 // Intern the new duplicate 287 Object beforeInternedValue = getValue(dupString2); 288 String internedString = dupString2.intern(); 289 if (internedString != dupString2) { 290 throw new RuntimeException("String should match"); 291 } 292 if (getValue(internedString) != getValue(baseString)) { 293 throw new RuntimeException("Values should match"); 294 } 295 296 // Check original value of interned string, to make sure 297 // deduplication happened on the interned string and not 298 // on the base string 299 if (beforeInternedValue == getValue(baseString)) { 300 throw new RuntimeException("Values should not match"); 301 } 302 303 System.out.println("End: InternedTest"); 304 } 305 306 public static OutputAnalyzer run() throws Exception { 307 return runTest("-Xlog:gc=debug,gc+stringdedup=trace", 308 "-XX:+UseStringDeduplication", 309 "-XX:StringDeduplicationAgeThreshold=" + DefaultAgeThreshold, 310 InternedTest.class.getName(), 311 "" + DefaultAgeThreshold); 312 } 313 } 314 315 /* 316 * Tests 317 */ 318 319 private static final int LargeNumberOfStrings = 10000; 320 private static final int SmallNumberOfStrings = 10; 321 322 private static final int MaxAgeThreshold = 15; 323 private static final int DefaultAgeThreshold = 3; 324 private static final int MinAgeThreshold = 1; 325 326 private static final int TooLowAgeThreshold = MinAgeThreshold - 1; 327 private static final int TooHighAgeThreshold = MaxAgeThreshold + 1; 328 329 public static void testYoungGC() throws Exception { 330 // Do young GC to age strings to provoke deduplication 331 OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, 332 DefaultAgeThreshold, 333 YoungGC, 334 "-Xlog:gc,gc+stringdedup=trace"); 335 output.shouldNotContain("Full GC"); 336 output.shouldContain("Pause Young (G1 Evacuation Pause)"); 337 output.shouldContain("Concurrent String Deduplication"); 338 output.shouldContain("Deduplicated:"); 339 output.shouldHaveExitValue(0); 340 } 341 342 public static void testFullGC() throws Exception { 343 // Do full GC to age strings to provoke deduplication 344 OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, 345 DefaultAgeThreshold, 346 FullGC, 347 "-Xlog:gc,gc+stringdedup=trace"); 348 output.shouldNotContain("Pause Young (G1 Evacuation Pause)"); 349 output.shouldContain("Full GC"); 350 output.shouldContain("Concurrent String Deduplication"); 351 output.shouldContain("Deduplicated:"); 352 output.shouldHaveExitValue(0); 353 } 354 355 public static void testTableResize() throws Exception { 356 // Test with StringDeduplicationResizeALot 357 OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, 358 DefaultAgeThreshold, 359 YoungGC, 360 "-Xlog:gc,gc+stringdedup=trace", 361 "-XX:+StringDeduplicationResizeALot"); 362 output.shouldContain("Concurrent String Deduplication"); 363 output.shouldContain("Deduplicated:"); 364 output.shouldNotContain("Resize Count: 0"); 365 output.shouldHaveExitValue(0); 366 } 367 368 public static void testTableRehash() throws Exception { 369 // Test with StringDeduplicationRehashALot 370 OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, 371 DefaultAgeThreshold, 372 YoungGC, 373 "-Xlog:gc,gc+stringdedup=trace", 374 "-XX:+StringDeduplicationRehashALot"); 375 output.shouldContain("Concurrent String Deduplication"); 376 output.shouldContain("Deduplicated:"); 377 output.shouldNotContain("Rehash Count: 0"); 378 output.shouldNotContain("Hash Seed: 0x0"); 379 output.shouldHaveExitValue(0); 380 } 381 382 public static void testAgeThreshold() throws Exception { 383 OutputAnalyzer output; 384 385 // Test with max age theshold 386 output = DeduplicationTest.run(SmallNumberOfStrings, 387 MaxAgeThreshold, 388 YoungGC, 389 "-Xlog:gc,gc+stringdedup=trace"); 390 output.shouldContain("Concurrent String Deduplication"); 391 output.shouldContain("Deduplicated:"); 392 output.shouldHaveExitValue(0); 393 394 // Test with min age theshold 395 output = DeduplicationTest.run(SmallNumberOfStrings, 396 MinAgeThreshold, 397 YoungGC, 398 "-Xlog:gc,gc+stringdedup=trace"); 399 output.shouldContain("Concurrent String Deduplication"); 400 output.shouldContain("Deduplicated:"); 401 output.shouldHaveExitValue(0); 402 403 // Test with too low age threshold 404 output = DeduplicationTest.run(SmallNumberOfStrings, 405 TooLowAgeThreshold, 406 YoungGC); 407 output.shouldContain("outside the allowed range"); 408 output.shouldHaveExitValue(1); 409 410 // Test with too high age threshold 411 output = DeduplicationTest.run(SmallNumberOfStrings, 412 TooHighAgeThreshold, 413 YoungGC); 414 output.shouldContain("outside the allowed range"); 415 output.shouldHaveExitValue(1); 416 } 417 418 public static void testPrintOptions() throws Exception { 419 OutputAnalyzer output; 420 421 // Test without -Xlog:gc 422 output = DeduplicationTest.run(SmallNumberOfStrings, 423 DefaultAgeThreshold, 424 YoungGC); 425 output.shouldNotContain("Concurrent String Deduplication"); 426 output.shouldNotContain("Deduplicated:"); 427 output.shouldHaveExitValue(0); 428 429 // Test with -Xlog:gc+stringdedup 430 output = DeduplicationTest.run(SmallNumberOfStrings, 431 DefaultAgeThreshold, 432 YoungGC, 433 "-Xlog:gc+stringdedup"); 434 output.shouldContain("Concurrent String Deduplication"); 435 output.shouldNotContain("Deduplicated:"); 436 output.shouldHaveExitValue(0); 437 } 438 439 public static void testInterned() throws Exception { 440 // Test that interned strings are deduplicated before being interned 441 OutputAnalyzer output = InternedTest.run(); 442 output.shouldHaveExitValue(0); 443 } 444 }