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