1 /* 2 * Copyright (c) 2014, 2019, 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 package gc.g1; 25 26 import jdk.test.lib.Asserts; 27 import jdk.test.lib.Platform; 28 import jdk.test.lib.process.ProcessTools; 29 import jdk.test.lib.process.OutputAnalyzer; 30 import jtreg.SkippedException; 31 32 import java.io.IOException; 33 import java.lang.management.ManagementFactory; 34 import java.lang.management.MemoryUsage; 35 import java.text.DecimalFormat; 36 import java.text.DecimalFormatSymbols; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.LinkedList; 40 import java.util.List; 41 import jdk.internal.misc.Unsafe; // for ADDRESS_SIZE 42 import sun.hotspot.WhiteBox; 43 44 public class TestShrinkAuxiliaryData { 45 46 private static final int REGION_SIZE = 1024 * 1024; 47 48 private final static String[] initialOpts = new String[]{ 49 "-XX:NewSize=16m", 50 "-XX:MinHeapFreeRatio=10", 51 "-XX:MaxHeapFreeRatio=11", 52 "-XX:+UseG1GC", 53 "-XX:G1HeapRegionSize=" + REGION_SIZE, 54 "-XX:-ExplicitGCInvokesConcurrent", 55 "-Xlog:gc=debug", 56 "-XX:+UnlockDiagnosticVMOptions", 57 "-XX:+WhiteBoxAPI", 58 "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED", 59 "-Xbootclasspath/a:.", 60 }; 61 62 private final int hotCardTableSize; 63 TestShrinkAuxiliaryData(int hotCardTableSize)64 protected TestShrinkAuxiliaryData(int hotCardTableSize) { 65 this.hotCardTableSize = hotCardTableSize; 66 } 67 test()68 protected void test() throws Exception { 69 ArrayList<String> vmOpts = new ArrayList<>(); 70 Collections.addAll(vmOpts, initialOpts); 71 72 int maxCacheSize = Math.max(0, Math.min(31, getMaxCacheSize())); 73 if (maxCacheSize < hotCardTableSize) { 74 throw new SkippedException(String.format( 75 "Skiping test for %d cache size due max cache size %d", 76 hotCardTableSize, maxCacheSize)); 77 } 78 79 printTestInfo(maxCacheSize); 80 81 vmOpts.add("-XX:G1ConcRSLogCacheSize=" + hotCardTableSize); 82 83 // for 32 bits ObjectAlignmentInBytes is not a option 84 if (Platform.is32bit()) { 85 ArrayList<String> vmOptsWithoutAlign = new ArrayList<>(vmOpts); 86 vmOptsWithoutAlign.add(ShrinkAuxiliaryDataTest.class.getName()); 87 performTest(vmOptsWithoutAlign); 88 return; 89 } 90 91 for (int alignment = 3; alignment <= 8; alignment++) { 92 ArrayList<String> vmOptsWithAlign = new ArrayList<>(vmOpts); 93 vmOptsWithAlign.add("-XX:ObjectAlignmentInBytes=" 94 + (int) Math.pow(2, alignment)); 95 vmOptsWithAlign.add(ShrinkAuxiliaryDataTest.class.getName()); 96 97 performTest(vmOptsWithAlign); 98 } 99 } 100 performTest(List<String> opts)101 private void performTest(List<String> opts) throws Exception { 102 ProcessBuilder pb 103 = ProcessTools.createJavaProcessBuilder(true, 104 opts.toArray(new String[opts.size()]) 105 ); 106 107 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 108 System.out.println(output.getStdout()); 109 System.err.println(output.getStderr()); 110 output.shouldHaveExitValue(0); 111 } 112 printTestInfo(int maxCacheSize)113 private void printTestInfo(int maxCacheSize) { 114 115 DecimalFormat grouped = new DecimalFormat("000,000"); 116 DecimalFormatSymbols formatSymbols = grouped.getDecimalFormatSymbols(); 117 formatSymbols.setGroupingSeparator(' '); 118 grouped.setDecimalFormatSymbols(formatSymbols); 119 120 System.out.format( 121 "Test will use %s bytes of memory of %s available%n" 122 + "Available memory is %s with %d bytes pointer size - can save %s pointers%n" 123 + "Max cache size: 2^%d = %s elements%n", 124 grouped.format(ShrinkAuxiliaryDataTest.getMemoryUsedByTest()), 125 grouped.format(Runtime.getRuntime().maxMemory()), 126 grouped.format(Runtime.getRuntime().maxMemory() 127 - ShrinkAuxiliaryDataTest.getMemoryUsedByTest()), 128 Unsafe.ADDRESS_SIZE, 129 grouped.format((Runtime.getRuntime().freeMemory() 130 - ShrinkAuxiliaryDataTest.getMemoryUsedByTest()) 131 / Unsafe.ADDRESS_SIZE), 132 maxCacheSize, 133 grouped.format((int) Math.pow(2, maxCacheSize)) 134 ); 135 } 136 137 /** 138 * Detects maximum possible size of G1ConcRSLogCacheSize available for 139 * current process based on maximum available process memory size 140 * 141 * @return power of two 142 */ getMaxCacheSize()143 private static int getMaxCacheSize() { 144 long availableMemory = Runtime.getRuntime().freeMemory() 145 - ShrinkAuxiliaryDataTest.getMemoryUsedByTest() - 1l; 146 if (availableMemory <= 0) { 147 return 0; 148 } 149 150 long availablePointersCount = availableMemory / Unsafe.ADDRESS_SIZE; 151 return (63 - (int) Long.numberOfLeadingZeros(availablePointersCount)); 152 } 153 154 static class ShrinkAuxiliaryDataTest { 155 main(String[] args)156 public static void main(String[] args) throws IOException { 157 158 ShrinkAuxiliaryDataTest testCase = new ShrinkAuxiliaryDataTest(); 159 160 if (!testCase.checkEnvApplicability()) { 161 return; 162 } 163 164 testCase.test(); 165 } 166 167 /** 168 * Checks is this environment suitable to run this test 169 * - memory is enough to decommit (page size is not big) 170 * - RSet cache size is not too big 171 * 172 * @return true if test could run, false if test should be skipped 173 */ checkEnvApplicability()174 protected boolean checkEnvApplicability() { 175 176 int pageSize = WhiteBox.getWhiteBox().getVMPageSize(); 177 System.out.println( "Page size = " + pageSize 178 + " region size = " + REGION_SIZE 179 + " aux data ~= " + (REGION_SIZE * 3 / 100)); 180 // If auxdata size will be less than page size it wouldn't decommit. 181 // Auxiliary data size is about ~3.6% of heap size. 182 if (pageSize >= REGION_SIZE * 3 / 100) { 183 System.out.format("Skipping test for too large page size = %d", 184 pageSize 185 ); 186 return false; 187 } 188 189 if (REGION_SIZE * REGIONS_TO_ALLOCATE > Runtime.getRuntime().maxMemory()) { 190 System.out.format("Skipping test for too low available memory. " 191 + "Need %d, available %d", 192 REGION_SIZE * REGIONS_TO_ALLOCATE, 193 Runtime.getRuntime().maxMemory() 194 ); 195 return false; 196 } 197 198 return true; 199 } 200 201 class GarbageObject { 202 203 private final List<byte[]> payload = new ArrayList<>(); 204 private final List<GarbageObject> ref = new LinkedList<>(); 205 GarbageObject(int size)206 public GarbageObject(int size) { 207 payload.add(new byte[size]); 208 } 209 addRef(GarbageObject g)210 public void addRef(GarbageObject g) { 211 ref.add(g); 212 } 213 mutate()214 public void mutate() { 215 if (!payload.isEmpty() && payload.get(0).length > 0) { 216 payload.get(0)[0] = (byte) (Math.random() * Byte.MAX_VALUE); 217 } 218 } 219 } 220 221 private final List<GarbageObject> garbage = new ArrayList<>(); 222 test()223 public void test() throws IOException { 224 225 MemoryUsage muFull, muFree, muAuxDataFull, muAuxDataFree; 226 float auxFull, auxFree; 227 228 allocate(); 229 link(); 230 mutate(); 231 232 muFull = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); 233 long numUsedRegions = WhiteBox.getWhiteBox().g1NumMaxRegions() 234 - WhiteBox.getWhiteBox().g1NumFreeRegions(); 235 muAuxDataFull = WhiteBox.getWhiteBox().g1AuxiliaryMemoryUsage(); 236 auxFull = (float)muAuxDataFull.getUsed() / numUsedRegions; 237 238 System.out.format("Full aux data ratio= %f, regions max= %d, used= %d\n", 239 auxFull, WhiteBox.getWhiteBox().g1NumMaxRegions(), numUsedRegions 240 ); 241 242 deallocate(); 243 System.gc(); 244 245 muFree = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); 246 muAuxDataFree = WhiteBox.getWhiteBox().g1AuxiliaryMemoryUsage(); 247 248 numUsedRegions = WhiteBox.getWhiteBox().g1NumMaxRegions() 249 - WhiteBox.getWhiteBox().g1NumFreeRegions(); 250 auxFree = (float)muAuxDataFree.getUsed() / numUsedRegions; 251 252 System.out.format("Free aux data ratio= %f, regions max= %d, used= %d\n", 253 auxFree, WhiteBox.getWhiteBox().g1NumMaxRegions(), numUsedRegions 254 ); 255 256 Asserts.assertLessThanOrEqual(muFree.getCommitted(), muFull.getCommitted(), 257 String.format("heap decommit failed - full > free: %d > %d", 258 muFree.getCommitted(), muFull.getCommitted() 259 ) 260 ); 261 262 System.out.format("State used committed\n"); 263 System.out.format("Full aux data: %10d %10d\n", muAuxDataFull.getUsed(), muAuxDataFull.getCommitted()); 264 System.out.format("Free aux data: %10d %10d\n", muAuxDataFree.getUsed(), muAuxDataFree.getCommitted()); 265 266 // if decommited check that aux data has same ratio 267 if (muFree.getCommitted() < muFull.getCommitted()) { 268 Asserts.assertLessThanOrEqual(auxFree, auxFull, 269 String.format("auxiliary data decommit failed - full > free: %f > %f", 270 auxFree, auxFull 271 ) 272 ); 273 } 274 } 275 allocate()276 private void allocate() { 277 for (int r = 0; r < REGIONS_TO_ALLOCATE; r++) { 278 for (int i = 0; i < NUM_OBJECTS_PER_REGION; i++) { 279 GarbageObject g = new GarbageObject(REGION_SIZE 280 / NUM_OBJECTS_PER_REGION); 281 garbage.add(g); 282 } 283 } 284 } 285 286 /** 287 * Iterate through all allocated objects, and link to objects in another 288 * regions 289 */ link()290 private void link() { 291 for (int ig = 0; ig < garbage.size(); ig++) { 292 int regionNumber = ig / NUM_OBJECTS_PER_REGION; 293 294 for (int i = 0; i < NUM_LINKS; i++) { 295 int regionToLink; 296 do { 297 regionToLink = (int) (Math.random() * REGIONS_TO_ALLOCATE); 298 } while (regionToLink == regionNumber); 299 300 // get random garbage object from random region 301 garbage.get(ig).addRef(garbage.get(regionToLink 302 * NUM_OBJECTS_PER_REGION + (int) (Math.random() 303 * NUM_OBJECTS_PER_REGION))); 304 } 305 } 306 } 307 mutate()308 private void mutate() { 309 for (int ig = 0; ig < garbage.size(); ig++) { 310 garbage.get(ig).mutate(); 311 } 312 } 313 deallocate()314 private void deallocate() { 315 garbage.clear(); 316 System.gc(); 317 } 318 getMemoryUsedByTest()319 static long getMemoryUsedByTest() { 320 return REGIONS_TO_ALLOCATE * REGION_SIZE; 321 } 322 323 private static final int REGIONS_TO_ALLOCATE = 100; 324 private static final int NUM_OBJECTS_PER_REGION = 10; 325 private static final int NUM_LINKS = 20; // how many links create for each object 326 } 327 } 328