Tue, 18 Mar 2014 19:07:22 +0100
8029075: String deduplication in G1
Summary: Implementation of JEP 192, http://openjdk.java.net/jeps/192
Reviewed-by: brutisso, tschatzl, coleenp
pliden@6413 | 1 | /* |
pliden@6413 | 2 | * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. |
pliden@6413 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
pliden@6413 | 4 | * |
pliden@6413 | 5 | * This code is free software; you can redistribute it and/or modify it |
pliden@6413 | 6 | * under the terms of the GNU General Public License version 2 only, as |
pliden@6413 | 7 | * published by the Free Software Foundation. |
pliden@6413 | 8 | * |
pliden@6413 | 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
pliden@6413 | 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
pliden@6413 | 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
pliden@6413 | 12 | * version 2 for more details (a copy is included in the LICENSE file that |
pliden@6413 | 13 | * accompanied this code). |
pliden@6413 | 14 | * |
pliden@6413 | 15 | * You should have received a copy of the GNU General Public License version |
pliden@6413 | 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
pliden@6413 | 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
pliden@6413 | 18 | * |
pliden@6413 | 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
pliden@6413 | 20 | * or visit www.oracle.com if you need additional information or have any |
pliden@6413 | 21 | * questions. |
pliden@6413 | 22 | */ |
pliden@6413 | 23 | |
pliden@6413 | 24 | /* |
pliden@6413 | 25 | * Common code for string deduplication tests |
pliden@6413 | 26 | */ |
pliden@6413 | 27 | |
pliden@6413 | 28 | import java.lang.management.*; |
pliden@6413 | 29 | import java.lang.reflect.*; |
pliden@6413 | 30 | import java.security.*; |
pliden@6413 | 31 | import java.util.*; |
pliden@6413 | 32 | import com.oracle.java.testlibrary.*; |
pliden@6413 | 33 | import sun.misc.*; |
pliden@6413 | 34 | |
pliden@6413 | 35 | class TestStringDeduplicationTools { |
pliden@6413 | 36 | private static final String YoungGC = "YoungGC"; |
pliden@6413 | 37 | private static final String FullGC = "FullGC"; |
pliden@6413 | 38 | |
pliden@6413 | 39 | private static final int Xmn = 50; // MB |
pliden@6413 | 40 | private static final int Xms = 100; // MB |
pliden@6413 | 41 | private static final int Xmx = 100; // MB |
pliden@6413 | 42 | private static final int MB = 1024 * 1024; |
pliden@6413 | 43 | private static final int StringLength = 50; |
pliden@6413 | 44 | |
pliden@6413 | 45 | private static Field valueField; |
pliden@6413 | 46 | private static Unsafe unsafe; |
pliden@6413 | 47 | private static byte[] dummy; |
pliden@6413 | 48 | |
pliden@6413 | 49 | static { |
pliden@6413 | 50 | try { |
pliden@6413 | 51 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); |
pliden@6413 | 52 | field.setAccessible(true); |
pliden@6413 | 53 | unsafe = (Unsafe)field.get(null); |
pliden@6413 | 54 | |
pliden@6413 | 55 | valueField = String.class.getDeclaredField("value"); |
pliden@6413 | 56 | valueField.setAccessible(true); |
pliden@6413 | 57 | } catch (Exception e) { |
pliden@6413 | 58 | throw new RuntimeException(e); |
pliden@6413 | 59 | } |
pliden@6413 | 60 | } |
pliden@6413 | 61 | |
pliden@6413 | 62 | private static Object getValue(String string) { |
pliden@6413 | 63 | try { |
pliden@6413 | 64 | return valueField.get(string); |
pliden@6413 | 65 | } catch (Exception e) { |
pliden@6413 | 66 | throw new RuntimeException(e); |
pliden@6413 | 67 | } |
pliden@6413 | 68 | } |
pliden@6413 | 69 | |
pliden@6413 | 70 | private static void doFullGc(int numberOfTimes) { |
pliden@6413 | 71 | for (int i = 0; i < numberOfTimes; i++) { |
pliden@6413 | 72 | System.out.println("Begin: Full GC " + (i + 1) + "/" + numberOfTimes); |
pliden@6413 | 73 | System.gc(); |
pliden@6413 | 74 | System.out.println("End: Full GC " + (i + 1) + "/" + numberOfTimes); |
pliden@6413 | 75 | } |
pliden@6413 | 76 | } |
pliden@6413 | 77 | |
pliden@6413 | 78 | private static void doYoungGc(int numberOfTimes) { |
pliden@6413 | 79 | // Provoke at least numberOfTimes young GCs |
pliden@6413 | 80 | final int objectSize = 128; |
pliden@6413 | 81 | final int maxObjectInYoung = (Xmn * MB) / objectSize; |
pliden@6413 | 82 | for (int i = 0; i < numberOfTimes; i++) { |
pliden@6413 | 83 | System.out.println("Begin: Young GC " + (i + 1) + "/" + numberOfTimes); |
pliden@6413 | 84 | for (int j = 0; j < maxObjectInYoung + 1; j++) { |
pliden@6413 | 85 | dummy = new byte[objectSize]; |
pliden@6413 | 86 | } |
pliden@6413 | 87 | System.out.println("End: Young GC " + (i + 1) + "/" + numberOfTimes); |
pliden@6413 | 88 | } |
pliden@6413 | 89 | } |
pliden@6413 | 90 | |
pliden@6413 | 91 | private static void forceDeduplication(int ageThreshold, String gcType) { |
pliden@6413 | 92 | // Force deduplication to happen by either causing a FullGC or a YoungGC. |
pliden@6413 | 93 | // We do several collections to also provoke a situation where the the |
pliden@6413 | 94 | // deduplication thread needs to yield while processing the queue. This |
pliden@6413 | 95 | // also tests that the references in the deduplication queue are adjusted |
pliden@6413 | 96 | // accordingly. |
pliden@6413 | 97 | if (gcType.equals(FullGC)) { |
pliden@6413 | 98 | doFullGc(3); |
pliden@6413 | 99 | } else { |
pliden@6413 | 100 | doYoungGc(ageThreshold + 3); |
pliden@6413 | 101 | } |
pliden@6413 | 102 | } |
pliden@6413 | 103 | |
pliden@6413 | 104 | private static String generateString(int id) { |
pliden@6413 | 105 | StringBuilder builder = new StringBuilder(StringLength); |
pliden@6413 | 106 | |
pliden@6413 | 107 | builder.append("DeduplicationTestString:" + id + ":"); |
pliden@6413 | 108 | |
pliden@6413 | 109 | while (builder.length() < StringLength) { |
pliden@6413 | 110 | builder.append('X'); |
pliden@6413 | 111 | } |
pliden@6413 | 112 | |
pliden@6413 | 113 | return builder.toString(); |
pliden@6413 | 114 | } |
pliden@6413 | 115 | |
pliden@6413 | 116 | private static ArrayList<String> createStrings(int total, int unique) { |
pliden@6413 | 117 | System.out.println("Creating strings: total=" + total + ", unique=" + unique); |
pliden@6413 | 118 | if (total % unique != 0) { |
pliden@6413 | 119 | throw new RuntimeException("Total must be divisible by unique"); |
pliden@6413 | 120 | } |
pliden@6413 | 121 | |
pliden@6413 | 122 | ArrayList<String> list = new ArrayList<String>(total); |
pliden@6413 | 123 | for (int j = 0; j < total / unique; j++) { |
pliden@6413 | 124 | for (int i = 0; i < unique; i++) { |
pliden@6413 | 125 | list.add(generateString(i)); |
pliden@6413 | 126 | } |
pliden@6413 | 127 | } |
pliden@6413 | 128 | |
pliden@6413 | 129 | return list; |
pliden@6413 | 130 | } |
pliden@6413 | 131 | |
pliden@6413 | 132 | private static void verifyStrings(ArrayList<String> list, int uniqueExpected) { |
pliden@6413 | 133 | for (;;) { |
pliden@6413 | 134 | // Check number of deduplicated strings |
pliden@6413 | 135 | ArrayList<Object> unique = new ArrayList<Object>(uniqueExpected); |
pliden@6413 | 136 | for (String string: list) { |
pliden@6413 | 137 | Object value = getValue(string); |
pliden@6413 | 138 | boolean uniqueValue = true; |
pliden@6413 | 139 | for (Object obj: unique) { |
pliden@6413 | 140 | if (obj == value) { |
pliden@6413 | 141 | uniqueValue = false; |
pliden@6413 | 142 | break; |
pliden@6413 | 143 | } |
pliden@6413 | 144 | } |
pliden@6413 | 145 | |
pliden@6413 | 146 | if (uniqueValue) { |
pliden@6413 | 147 | unique.add(value); |
pliden@6413 | 148 | } |
pliden@6413 | 149 | } |
pliden@6413 | 150 | |
pliden@6413 | 151 | System.out.println("Verifying strings: total=" + list.size() + |
pliden@6413 | 152 | ", uniqueFound=" + unique.size() + |
pliden@6413 | 153 | ", uniqueExpected=" + uniqueExpected); |
pliden@6413 | 154 | |
pliden@6413 | 155 | if (unique.size() == uniqueExpected) { |
pliden@6413 | 156 | System.out.println("Deduplication completed"); |
pliden@6413 | 157 | break; |
pliden@6413 | 158 | } else { |
pliden@6413 | 159 | System.out.println("Deduplication not completed, waiting..."); |
pliden@6413 | 160 | |
pliden@6413 | 161 | // Give the deduplication thread time to complete |
pliden@6413 | 162 | try { |
pliden@6413 | 163 | Thread.sleep(1000); |
pliden@6413 | 164 | } catch (Exception e) { |
pliden@6413 | 165 | throw new RuntimeException(e); |
pliden@6413 | 166 | } |
pliden@6413 | 167 | } |
pliden@6413 | 168 | } |
pliden@6413 | 169 | } |
pliden@6413 | 170 | |
pliden@6413 | 171 | private static OutputAnalyzer runTest(String... extraArgs) throws Exception { |
pliden@6413 | 172 | String[] defaultArgs = new String[] { |
pliden@6413 | 173 | "-Xmn" + Xmn + "m", |
pliden@6413 | 174 | "-Xms" + Xms + "m", |
pliden@6413 | 175 | "-Xmx" + Xmx + "m", |
pliden@6413 | 176 | "-XX:+UseG1GC", |
pliden@6413 | 177 | "-XX:+UnlockDiagnosticVMOptions", |
pliden@6413 | 178 | "-XX:+VerifyAfterGC" // Always verify after GC |
pliden@6413 | 179 | }; |
pliden@6413 | 180 | |
pliden@6413 | 181 | ArrayList<String> args = new ArrayList<String>(); |
pliden@6413 | 182 | args.addAll(Arrays.asList(defaultArgs)); |
pliden@6413 | 183 | args.addAll(Arrays.asList(extraArgs)); |
pliden@6413 | 184 | |
pliden@6413 | 185 | ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(args.toArray(new String[args.size()])); |
pliden@6413 | 186 | OutputAnalyzer output = new OutputAnalyzer(pb.start()); |
pliden@6413 | 187 | System.err.println(output.getStderr()); |
pliden@6413 | 188 | System.out.println(output.getStdout()); |
pliden@6413 | 189 | return output; |
pliden@6413 | 190 | } |
pliden@6413 | 191 | |
pliden@6413 | 192 | private static class DeduplicationTest { |
pliden@6413 | 193 | public static void main(String[] args) { |
pliden@6413 | 194 | System.out.println("Begin: DeduplicationTest"); |
pliden@6413 | 195 | |
pliden@6413 | 196 | final int numberOfStrings = Integer.parseUnsignedInt(args[0]); |
pliden@6413 | 197 | final int numberOfUniqueStrings = Integer.parseUnsignedInt(args[1]); |
pliden@6413 | 198 | final int ageThreshold = Integer.parseUnsignedInt(args[2]); |
pliden@6413 | 199 | final String gcType = args[3]; |
pliden@6413 | 200 | |
pliden@6413 | 201 | ArrayList<String> list = createStrings(numberOfStrings, numberOfUniqueStrings); |
pliden@6413 | 202 | forceDeduplication(ageThreshold, gcType); |
pliden@6413 | 203 | verifyStrings(list, numberOfUniqueStrings); |
pliden@6413 | 204 | |
pliden@6413 | 205 | System.out.println("End: DeduplicationTest"); |
pliden@6413 | 206 | } |
pliden@6413 | 207 | |
pliden@6413 | 208 | public static OutputAnalyzer run(int numberOfStrings, int ageThreshold, String gcType, String... extraArgs) throws Exception { |
pliden@6413 | 209 | String[] defaultArgs = new String[] { |
pliden@6413 | 210 | "-XX:+UseStringDeduplication", |
pliden@6413 | 211 | "-XX:StringDeduplicationAgeThreshold=" + ageThreshold, |
pliden@6413 | 212 | DeduplicationTest.class.getName(), |
pliden@6413 | 213 | "" + numberOfStrings, |
pliden@6413 | 214 | "" + numberOfStrings / 2, |
pliden@6413 | 215 | "" + ageThreshold, |
pliden@6413 | 216 | gcType |
pliden@6413 | 217 | }; |
pliden@6413 | 218 | |
pliden@6413 | 219 | ArrayList<String> args = new ArrayList<String>(); |
pliden@6413 | 220 | args.addAll(Arrays.asList(extraArgs)); |
pliden@6413 | 221 | args.addAll(Arrays.asList(defaultArgs)); |
pliden@6413 | 222 | |
pliden@6413 | 223 | return runTest(args.toArray(new String[args.size()])); |
pliden@6413 | 224 | } |
pliden@6413 | 225 | } |
pliden@6413 | 226 | |
pliden@6413 | 227 | private static class InternedTest { |
pliden@6413 | 228 | public static void main(String[] args) { |
pliden@6413 | 229 | // This test verifies that interned strings are always |
pliden@6413 | 230 | // deduplicated when being interned, and never after |
pliden@6413 | 231 | // being interned. |
pliden@6413 | 232 | |
pliden@6413 | 233 | System.out.println("Begin: InternedTest"); |
pliden@6413 | 234 | |
pliden@6413 | 235 | final int ageThreshold = Integer.parseUnsignedInt(args[0]); |
pliden@6413 | 236 | final String baseString = "DeduplicationTestString:" + InternedTest.class.getName(); |
pliden@6413 | 237 | |
pliden@6413 | 238 | // Create duplicate of baseString |
pliden@6413 | 239 | StringBuilder sb1 = new StringBuilder(baseString); |
pliden@6413 | 240 | String dupString1 = sb1.toString(); |
pliden@6413 | 241 | if (getValue(dupString1) == getValue(baseString)) { |
pliden@6413 | 242 | throw new RuntimeException("Values should not match"); |
pliden@6413 | 243 | } |
pliden@6413 | 244 | |
pliden@6413 | 245 | // Force baseString to be inspected for deduplication |
pliden@6413 | 246 | // and be inserted into the deduplication hashtable. |
pliden@6413 | 247 | forceDeduplication(ageThreshold, FullGC); |
pliden@6413 | 248 | |
pliden@6413 | 249 | // Wait for deduplication to occur |
pliden@6413 | 250 | while (getValue(dupString1) != getValue(baseString)) { |
pliden@6413 | 251 | System.out.println("Waiting..."); |
pliden@6413 | 252 | try { |
pliden@6413 | 253 | Thread.sleep(100); |
pliden@6413 | 254 | } catch (Exception e) { |
pliden@6413 | 255 | throw new RuntimeException(e); |
pliden@6413 | 256 | } |
pliden@6413 | 257 | } |
pliden@6413 | 258 | |
pliden@6413 | 259 | // Create a new duplicate of baseString |
pliden@6413 | 260 | StringBuilder sb2 = new StringBuilder(baseString); |
pliden@6413 | 261 | String dupString2 = sb2.toString(); |
pliden@6413 | 262 | if (getValue(dupString2) == getValue(baseString)) { |
pliden@6413 | 263 | throw new RuntimeException("Values should not match"); |
pliden@6413 | 264 | } |
pliden@6413 | 265 | |
pliden@6413 | 266 | // Intern the new duplicate |
pliden@6413 | 267 | Object beforeInternedValue = getValue(dupString2); |
pliden@6413 | 268 | String internedString = dupString2.intern(); |
pliden@6413 | 269 | if (internedString != dupString2) { |
pliden@6413 | 270 | throw new RuntimeException("String should match"); |
pliden@6413 | 271 | } |
pliden@6413 | 272 | if (getValue(internedString) != getValue(baseString)) { |
pliden@6413 | 273 | throw new RuntimeException("Values should match"); |
pliden@6413 | 274 | } |
pliden@6413 | 275 | |
pliden@6413 | 276 | // Check original value of interned string, to make sure |
pliden@6413 | 277 | // deduplication happened on the interned string and not |
pliden@6413 | 278 | // on the base string |
pliden@6413 | 279 | if (beforeInternedValue == getValue(baseString)) { |
pliden@6413 | 280 | throw new RuntimeException("Values should not match"); |
pliden@6413 | 281 | } |
pliden@6413 | 282 | |
pliden@6413 | 283 | System.out.println("End: InternedTest"); |
pliden@6413 | 284 | } |
pliden@6413 | 285 | |
pliden@6413 | 286 | public static OutputAnalyzer run() throws Exception { |
pliden@6413 | 287 | return runTest("-XX:+PrintGC", |
pliden@6413 | 288 | "-XX:+PrintGCDetails", |
pliden@6413 | 289 | "-XX:+UseStringDeduplication", |
pliden@6413 | 290 | "-XX:+PrintStringDeduplicationStatistics", |
pliden@6413 | 291 | "-XX:StringDeduplicationAgeThreshold=" + DefaultAgeThreshold, |
pliden@6413 | 292 | InternedTest.class.getName(), |
pliden@6413 | 293 | "" + DefaultAgeThreshold); |
pliden@6413 | 294 | } |
pliden@6413 | 295 | } |
pliden@6413 | 296 | |
pliden@6413 | 297 | private static class MemoryUsageTest { |
pliden@6413 | 298 | public static void main(String[] args) { |
pliden@6413 | 299 | System.out.println("Begin: MemoryUsageTest"); |
pliden@6413 | 300 | |
pliden@6413 | 301 | final boolean useStringDeduplication = Boolean.parseBoolean(args[0]); |
pliden@6413 | 302 | final int numberOfStrings = LargeNumberOfStrings; |
pliden@6413 | 303 | final int numberOfUniqueStrings = 1; |
pliden@6413 | 304 | |
pliden@6413 | 305 | ArrayList<String> list = createStrings(numberOfStrings, numberOfUniqueStrings); |
pliden@6413 | 306 | forceDeduplication(DefaultAgeThreshold, FullGC); |
pliden@6413 | 307 | |
pliden@6413 | 308 | if (useStringDeduplication) { |
pliden@6413 | 309 | verifyStrings(list, numberOfUniqueStrings); |
pliden@6413 | 310 | } |
pliden@6413 | 311 | |
pliden@6413 | 312 | System.gc(); |
pliden@6413 | 313 | System.out.println("Heap Memory Usage: " + ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed()); |
pliden@6413 | 314 | |
pliden@6413 | 315 | System.out.println("End: MemoryUsageTest"); |
pliden@6413 | 316 | } |
pliden@6413 | 317 | |
pliden@6413 | 318 | public static OutputAnalyzer run(boolean useStringDeduplication) throws Exception { |
pliden@6413 | 319 | String[] extraArgs = new String[0]; |
pliden@6413 | 320 | |
pliden@6413 | 321 | if (useStringDeduplication) { |
pliden@6413 | 322 | extraArgs = new String[] { |
pliden@6413 | 323 | "-XX:+UseStringDeduplication", |
pliden@6413 | 324 | "-XX:+PrintStringDeduplicationStatistics", |
pliden@6413 | 325 | "-XX:StringDeduplicationAgeThreshold=" + DefaultAgeThreshold |
pliden@6413 | 326 | }; |
pliden@6413 | 327 | } |
pliden@6413 | 328 | |
pliden@6413 | 329 | String[] defaultArgs = new String[] { |
pliden@6413 | 330 | "-XX:+PrintGC", |
pliden@6413 | 331 | "-XX:+PrintGCDetails", |
pliden@6413 | 332 | MemoryUsageTest.class.getName(), |
pliden@6413 | 333 | "" + useStringDeduplication |
pliden@6413 | 334 | }; |
pliden@6413 | 335 | |
pliden@6413 | 336 | ArrayList<String> args = new ArrayList<String>(); |
pliden@6413 | 337 | args.addAll(Arrays.asList(extraArgs)); |
pliden@6413 | 338 | args.addAll(Arrays.asList(defaultArgs)); |
pliden@6413 | 339 | |
pliden@6413 | 340 | return runTest(args.toArray(new String[args.size()])); |
pliden@6413 | 341 | } |
pliden@6413 | 342 | } |
pliden@6413 | 343 | |
pliden@6413 | 344 | /* |
pliden@6413 | 345 | * Tests |
pliden@6413 | 346 | */ |
pliden@6413 | 347 | |
pliden@6413 | 348 | private static final int LargeNumberOfStrings = 10000; |
pliden@6413 | 349 | private static final int SmallNumberOfStrings = 10; |
pliden@6413 | 350 | |
pliden@6413 | 351 | private static final int MaxAgeThreshold = 15; |
pliden@6413 | 352 | private static final int DefaultAgeThreshold = 3; |
pliden@6413 | 353 | private static final int MinAgeThreshold = 1; |
pliden@6413 | 354 | |
pliden@6413 | 355 | private static final int TooLowAgeThreshold = MinAgeThreshold - 1; |
pliden@6413 | 356 | private static final int TooHighAgeThreshold = MaxAgeThreshold + 1; |
pliden@6413 | 357 | |
pliden@6413 | 358 | public static void testYoungGC() throws Exception { |
pliden@6413 | 359 | // Do young GC to age strings to provoke deduplication |
pliden@6413 | 360 | OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, |
pliden@6413 | 361 | DefaultAgeThreshold, |
pliden@6413 | 362 | YoungGC, |
pliden@6413 | 363 | "-XX:+PrintGC", |
pliden@6413 | 364 | "-XX:+PrintStringDeduplicationStatistics"); |
pliden@6413 | 365 | output.shouldNotContain("Full GC"); |
pliden@6413 | 366 | output.shouldContain("GC pause (G1 Evacuation Pause) (young)"); |
pliden@6413 | 367 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 368 | output.shouldContain("Deduplicated:"); |
pliden@6413 | 369 | output.shouldHaveExitValue(0); |
pliden@6413 | 370 | } |
pliden@6413 | 371 | |
pliden@6413 | 372 | public static void testFullGC() throws Exception { |
pliden@6413 | 373 | // Do full GC to age strings to provoke deduplication |
pliden@6413 | 374 | OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, |
pliden@6413 | 375 | DefaultAgeThreshold, |
pliden@6413 | 376 | FullGC, |
pliden@6413 | 377 | "-XX:+PrintGC", |
pliden@6413 | 378 | "-XX:+PrintStringDeduplicationStatistics"); |
pliden@6413 | 379 | output.shouldNotContain("GC pause (G1 Evacuation Pause) (young)"); |
pliden@6413 | 380 | output.shouldContain("Full GC"); |
pliden@6413 | 381 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 382 | output.shouldContain("Deduplicated:"); |
pliden@6413 | 383 | output.shouldHaveExitValue(0); |
pliden@6413 | 384 | } |
pliden@6413 | 385 | |
pliden@6413 | 386 | public static void testTableResize() throws Exception { |
pliden@6413 | 387 | // Test with StringDeduplicationResizeALot |
pliden@6413 | 388 | OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, |
pliden@6413 | 389 | DefaultAgeThreshold, |
pliden@6413 | 390 | YoungGC, |
pliden@6413 | 391 | "-XX:+PrintGC", |
pliden@6413 | 392 | "-XX:+PrintStringDeduplicationStatistics", |
pliden@6413 | 393 | "-XX:+StringDeduplicationResizeALot"); |
pliden@6413 | 394 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 395 | output.shouldContain("Deduplicated:"); |
pliden@6413 | 396 | output.shouldNotContain("Resize Count: 0"); |
pliden@6413 | 397 | output.shouldHaveExitValue(0); |
pliden@6413 | 398 | } |
pliden@6413 | 399 | |
pliden@6413 | 400 | public static void testTableRehash() throws Exception { |
pliden@6413 | 401 | // Test with StringDeduplicationRehashALot |
pliden@6413 | 402 | OutputAnalyzer output = DeduplicationTest.run(LargeNumberOfStrings, |
pliden@6413 | 403 | DefaultAgeThreshold, |
pliden@6413 | 404 | YoungGC, |
pliden@6413 | 405 | "-XX:+PrintGC", |
pliden@6413 | 406 | "-XX:+PrintStringDeduplicationStatistics", |
pliden@6413 | 407 | "-XX:+StringDeduplicationRehashALot"); |
pliden@6413 | 408 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 409 | output.shouldContain("Deduplicated:"); |
pliden@6413 | 410 | output.shouldNotContain("Rehash Count: 0"); |
pliden@6413 | 411 | output.shouldNotContain("Hash Seed: 0x0"); |
pliden@6413 | 412 | output.shouldHaveExitValue(0); |
pliden@6413 | 413 | } |
pliden@6413 | 414 | |
pliden@6413 | 415 | public static void testAgeThreshold() throws Exception { |
pliden@6413 | 416 | OutputAnalyzer output; |
pliden@6413 | 417 | |
pliden@6413 | 418 | // Test with max age theshold |
pliden@6413 | 419 | output = DeduplicationTest.run(SmallNumberOfStrings, |
pliden@6413 | 420 | MaxAgeThreshold, |
pliden@6413 | 421 | YoungGC, |
pliden@6413 | 422 | "-XX:+PrintGC", |
pliden@6413 | 423 | "-XX:+PrintStringDeduplicationStatistics"); |
pliden@6413 | 424 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 425 | output.shouldContain("Deduplicated:"); |
pliden@6413 | 426 | output.shouldHaveExitValue(0); |
pliden@6413 | 427 | |
pliden@6413 | 428 | // Test with min age theshold |
pliden@6413 | 429 | output = DeduplicationTest.run(SmallNumberOfStrings, |
pliden@6413 | 430 | MinAgeThreshold, |
pliden@6413 | 431 | YoungGC, |
pliden@6413 | 432 | "-XX:+PrintGC", |
pliden@6413 | 433 | "-XX:+PrintStringDeduplicationStatistics"); |
pliden@6413 | 434 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 435 | output.shouldContain("Deduplicated:"); |
pliden@6413 | 436 | output.shouldHaveExitValue(0); |
pliden@6413 | 437 | |
pliden@6413 | 438 | // Test with too low age threshold |
pliden@6413 | 439 | output = DeduplicationTest.run(SmallNumberOfStrings, |
pliden@6413 | 440 | TooLowAgeThreshold, |
pliden@6413 | 441 | YoungGC); |
pliden@6413 | 442 | output.shouldContain("StringDeduplicationAgeThreshold of " + TooLowAgeThreshold + |
pliden@6413 | 443 | " is invalid; must be between " + MinAgeThreshold + " and " + MaxAgeThreshold); |
pliden@6413 | 444 | output.shouldHaveExitValue(1); |
pliden@6413 | 445 | |
pliden@6413 | 446 | // Test with too high age threshold |
pliden@6413 | 447 | output = DeduplicationTest.run(SmallNumberOfStrings, |
pliden@6413 | 448 | TooHighAgeThreshold, |
pliden@6413 | 449 | YoungGC); |
pliden@6413 | 450 | output.shouldContain("StringDeduplicationAgeThreshold of " + TooHighAgeThreshold + |
pliden@6413 | 451 | " is invalid; must be between " + MinAgeThreshold + " and " + MaxAgeThreshold); |
pliden@6413 | 452 | output.shouldHaveExitValue(1); |
pliden@6413 | 453 | } |
pliden@6413 | 454 | |
pliden@6413 | 455 | public static void testPrintOptions() throws Exception { |
pliden@6413 | 456 | OutputAnalyzer output; |
pliden@6413 | 457 | |
pliden@6413 | 458 | // Test without PrintGC and without PrintStringDeduplicationStatistics |
pliden@6413 | 459 | output = DeduplicationTest.run(SmallNumberOfStrings, |
pliden@6413 | 460 | DefaultAgeThreshold, |
pliden@6413 | 461 | YoungGC); |
pliden@6413 | 462 | output.shouldNotContain("GC concurrent-string-deduplication"); |
pliden@6413 | 463 | output.shouldNotContain("Deduplicated:"); |
pliden@6413 | 464 | output.shouldHaveExitValue(0); |
pliden@6413 | 465 | |
pliden@6413 | 466 | // Test with PrintGC but without PrintStringDeduplicationStatistics |
pliden@6413 | 467 | output = DeduplicationTest.run(SmallNumberOfStrings, |
pliden@6413 | 468 | DefaultAgeThreshold, |
pliden@6413 | 469 | YoungGC, |
pliden@6413 | 470 | "-XX:+PrintGC"); |
pliden@6413 | 471 | output.shouldContain("GC concurrent-string-deduplication"); |
pliden@6413 | 472 | output.shouldNotContain("Deduplicated:"); |
pliden@6413 | 473 | output.shouldHaveExitValue(0); |
pliden@6413 | 474 | } |
pliden@6413 | 475 | |
pliden@6413 | 476 | public static void testInterned() throws Exception { |
pliden@6413 | 477 | // Test that interned strings are deduplicated before being interned |
pliden@6413 | 478 | OutputAnalyzer output = InternedTest.run(); |
pliden@6413 | 479 | output.shouldHaveExitValue(0); |
pliden@6413 | 480 | } |
pliden@6413 | 481 | |
pliden@6413 | 482 | public static void testMemoryUsage() throws Exception { |
pliden@6413 | 483 | // Test that memory usage is reduced after deduplication |
pliden@6413 | 484 | OutputAnalyzer output; |
pliden@6413 | 485 | final String usagePattern = "Heap Memory Usage: (\\d+)"; |
pliden@6413 | 486 | |
pliden@6413 | 487 | // Run without deduplication |
pliden@6413 | 488 | output = MemoryUsageTest.run(false); |
pliden@6413 | 489 | output.shouldHaveExitValue(0); |
pliden@6413 | 490 | final long memoryUsageWithoutDedup = Long.parseLong(output.firstMatch(usagePattern, 1)); |
pliden@6413 | 491 | |
pliden@6413 | 492 | // Run with deduplication |
pliden@6413 | 493 | output = MemoryUsageTest.run(true); |
pliden@6413 | 494 | output.shouldHaveExitValue(0); |
pliden@6413 | 495 | final long memoryUsageWithDedup = Long.parseLong(output.firstMatch(usagePattern, 1)); |
pliden@6413 | 496 | |
pliden@6413 | 497 | // Calculate expected memory usage with deduplication enabled. This calculation does |
pliden@6413 | 498 | // not take alignment and padding into account, so it's a conservative estimate. |
pliden@6413 | 499 | final long sizeOfChar = 2; // bytes |
pliden@6413 | 500 | final long bytesSaved = (LargeNumberOfStrings - 1) * (StringLength * sizeOfChar + unsafe.ARRAY_CHAR_BASE_OFFSET); |
pliden@6413 | 501 | final long memoryUsageWithDedupExpected = memoryUsageWithoutDedup - bytesSaved; |
pliden@6413 | 502 | |
pliden@6413 | 503 | System.out.println("Memory usage summary:"); |
pliden@6413 | 504 | System.out.println(" memoryUsageWithoutDedup: " + memoryUsageWithoutDedup); |
pliden@6413 | 505 | System.out.println(" memoryUsageWithDedup: " + memoryUsageWithDedup); |
pliden@6413 | 506 | System.out.println(" memoryUsageWithDedupExpected: " + memoryUsageWithDedupExpected); |
pliden@6413 | 507 | |
pliden@6413 | 508 | if (memoryUsageWithDedup > memoryUsageWithDedupExpected) { |
pliden@6413 | 509 | throw new Exception("Unexpected memory usage, memoryUsageWithDedup should less or equal to memoryUsageWithDedupExpected"); |
pliden@6413 | 510 | } |
pliden@6413 | 511 | } |
pliden@6413 | 512 | } |