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