1 /*
   2  * Copyright (c) 2017, 2018, Red Hat, Inc. All rights reserved.
   3  *
   4  * This code is free software; you can redistribute it and/or modify it
   5  * under the terms of the GNU General Public License version 2 only, as
   6  * published by the Free Software Foundation.
   7  *
   8  * This code is distributed in the hope that it will be useful, but WITHOUT
   9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  11  * version 2 for more details (a copy is included in the LICENSE file that
  12  * accompanied this code).
  13  *
  14  * You should have received a copy of the GNU General Public License version
  15  * 2 along with this work; if not, write to the Free Software Foundation,
  16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  17  *
  18  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  19  * or visit www.oracle.com if you need additional information or have any
  20  * questions.
  21  *
  22  */
  23 
  24  /*
  25  * @test TestStringDedupStress
  26  * @summary Test Shenandoah string deduplication implementation
  27  * @key gc
  28  * @requires vm.gc.Shenandoah
  29  * @library /test/lib
  30  * @modules java.base/jdk.internal.misc:open
  31  * @modules java.base/java.lang:open
  32  *          java.management
  33  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  34  *                   -DtargetStrings=3000000
  35  *                   TestStringDedupStress
  36  *
  37  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  38  *                   -XX:ShenandoahGCHeuristics=aggressive -DtargetStrings=2000000
  39  *                   TestStringDedupStress
  40  *
  41  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  42  *                   -XX:ShenandoahGCHeuristics=aggressive -XX:+ShenandoahOOMDuringEvacALot -DtargetStrings=2000000
  43  *                    TestStringDedupStress
  44  *
  45  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  46  *                   -XX:ShenandoahGCHeuristics=static -DtargetStrings=4000000
  47  *                   TestStringDedupStress
  48  *
  49  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  50  *                   -XX:ShenandoahGCHeuristics=compact
  51  *                   TestStringDedupStress
  52  *
  53  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  54  *                   -XX:ShenandoahGCHeuristics=passive -XX:+ShenandoahDegeneratedGC -DtargetOverwrites=40000000
  55  *                   TestStringDedupStress
  56  *
  57  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  58  *                   -XX:ShenandoahGCHeuristics=passive -XX:-ShenandoahDegeneratedGC -DtargetOverwrites=40000000
  59  *                   TestStringDedupStress
  60  *
  61  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  62  *                   -XX:ShenandoahGCHeuristics=traversal
  63  *                   TestStringDedupStress
  64  *
  65  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  66  *                   -XX:ShenandoahUpdateRefsEarly=off -DtargetStrings=3000000
  67  *                   TestStringDedupStress
  68  *
  69  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  70  *                   -XX:ShenandoahGCHeuristics=compact -XX:ShenandoahUpdateRefsEarly=off -DtargetStrings=2000000
  71  *                   TestStringDedupStress
  72  *
  73  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  74  *                   -XX:ShenandoahGCHeuristics=aggressive -XX:ShenandoahUpdateRefsEarly=off -DtargetStrings=2000000
  75  *                   TestStringDedupStress
  76  *
  77  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  78  *                   -XX:ShenandoahGCHeuristics=static -XX:ShenandoahUpdateRefsEarly=off -DtargetOverwrites=4000000
  79  *                   TestStringDedupStress
  80  *
  81  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  82  *                   -XX:ShenandoahGCHeuristics=aggressive -XX:ShenandoahUpdateRefsEarly=off -XX:+ShenandoahOOMDuringEvacALot -DtargetStrings=2000000
  83  *                   TestStringDedupStress
  84  *
  85  * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+UseStringDeduplication -Xmx512M -Xlog:gc+stats
  86  *                   -XX:ShenandoahGCHeuristics=traversal -XX:+ShenandoahOOMDuringEvacALot -DtargetStrings=2000000
  87  *                   TestStringDedupStress
  88  */
  89 
  90 import java.lang.management.*;
  91 import java.lang.reflect.*;
  92 import java.util.*;
  93 
  94 import sun.misc.*;
  95 
  96 public class TestStringDedupStress {
  97     private static Field valueField;
  98     private static Unsafe unsafe;
  99 
 100     private static long TARGET_STRINGS = Long.getLong("targetStrings", 2_500_000);
 101     private static long TARGET_OVERWRITES = Long.getLong("targetOverwrites", 600_000);
 102     private static final long MAX_REWRITE_GC_CYCLES = 6;
 103 
 104     private static final int UNIQUE_STRINGS = 20;
 105 
 106     static {
 107         try {
 108             Field field = Unsafe.class.getDeclaredField("theUnsafe");
 109             field.setAccessible(true);
 110             unsafe = (Unsafe) field.get(null);
 111 
 112             valueField = String.class.getDeclaredField("value");
 113             valueField.setAccessible(true);
 114         } catch (Exception e) {
 115             throw new RuntimeException(e);
 116         }
 117     }
 118 
 119     private static Object getValue(String string) {
 120         try {
 121             return valueField.get(string);
 122         } catch (Exception e) {
 123             throw new RuntimeException(e);
 124         }
 125     }
 126 
 127     static class StringAndId {
 128         private String str;
 129         private int id;
 130 
 131         public StringAndId(String str, int id) {
 132             this.str = str;
 133             this.id = id;
 134         }
 135 
 136         public String str() {
 137             return str;
 138         }
 139 
 140         public int id() {
 141             return id;
 142         }
 143     }
 144 
 145     // Generate uniqueStrings number of strings
 146     private static void generateStrings(ArrayList<StringAndId> strs, int uniqueStrings) {
 147         Random rn = new Random();
 148         for (int u = 0; u < uniqueStrings; u++) {
 149             int n = rn.nextInt(uniqueStrings);
 150             strs.add(new StringAndId("Unique String " + n, n));
 151         }
 152     }
 153 
 154     private static int verifyDedepString(ArrayList<StringAndId> strs) {
 155         HashMap<Object, StringAndId> seen = new HashMap<>();
 156         int total = 0;
 157         int dedup = 0;
 158 
 159         for (StringAndId item : strs) {
 160             total++;
 161             StringAndId existingItem = seen.get(getValue(item.str()));
 162             if (existingItem == null) {
 163                 seen.put(getValue(item.str()), item);
 164             } else {
 165                 if (item.id() != existingItem.id() ||
 166                         !item.str().equals(existingItem.str())) {
 167                     System.out.println("StringDedup error:");
 168                     System.out.println("id: " + item.id() + " != " + existingItem.id());
 169                     System.out.println("or String: " + item.str() + " != " + existingItem.str());
 170                     throw new RuntimeException("StringDedup Test failed");
 171                 } else {
 172                     dedup++;
 173                 }
 174             }
 175         }
 176         System.out.println("Dedup: " + dedup + "/" + total + " unique: " + (total - dedup));
 177         return (total - dedup);
 178     }
 179 
 180     static volatile ArrayList<StringAndId> astrs = new ArrayList<>();
 181     static GarbageCollectorMXBean gcCycleMBean;
 182 
 183     public static void main(String[] args) {
 184         Random rn = new Random();
 185 
 186         for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
 187             if ("Shenandoah Cycles".equals(bean.getName())) {
 188                 gcCycleMBean = bean;
 189                 break;
 190             }
 191         }
 192 
 193         if (gcCycleMBean == null) {
 194             throw new RuntimeException("Can not find Shenandoah GC cycle mbean");
 195         }
 196 
 197         // Generate roughly TARGET_STRINGS strings, only UNIQUE_STRINGS are unique
 198         long genIters = TARGET_STRINGS / UNIQUE_STRINGS;
 199         for (long index = 0; index < genIters; index++) {
 200             generateStrings(astrs, UNIQUE_STRINGS);
 201         }
 202 
 203         long cycleBeforeRewrite = gcCycleMBean.getCollectionCount();
 204 
 205         for (long loop = 1; loop < TARGET_OVERWRITES; loop++) {
 206             int arrSize = astrs.size();
 207             int index = rn.nextInt(arrSize);
 208             StringAndId item = astrs.get(index);
 209             int n = rn.nextInt(UNIQUE_STRINGS);
 210             item.str = "Unique String " + n;
 211             item.id = n;
 212 
 213             if (loop % 1000 == 0) {
 214                 // enough GC cycles for rewritten strings to be deduplicated
 215                 if (gcCycleMBean.getCollectionCount() - cycleBeforeRewrite >= MAX_REWRITE_GC_CYCLES) {
 216                     break;
 217                 }
 218             }
 219         }
 220         verifyDedepString(astrs);
 221     }
 222 }