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 /**
27  * @test TestShrinkDefragmentedHeap
28  * @bug 8038423 8129590
29  * @summary Verify that heap shrinks after GC in the presence of fragmentation due to humongous objects
30  *     1. allocate small objects mixed with humongous ones
31  *        "ssssHssssHssssHssssHssssHssssHssssH"
32  *     2. release all allocated object except the last humongous one
33  *        "..................................H"
34  *     3. invoke gc and check that memory returned to the system (amount of committed memory got down)
35  *
36  * @library /test/lib /
37  * @requires vm.gc.G1
38  * @modules java.base/jdk.internal.misc
39  *          java.management/sun.management
40  * @run main gc.g1.TestShrinkDefragmentedHeap
41  */
42 import java.lang.management.ManagementFactory;
43 import java.lang.management.MemoryUsage;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.text.NumberFormat;
47 import static jdk.test.lib.Asserts.*;
48 import jdk.test.lib.process.OutputAnalyzer;
49 import jdk.test.lib.process.ProcessTools;
50 import com.sun.management.HotSpotDiagnosticMXBean;
51 import gc.testlibrary.Helpers;
52 
53 public class TestShrinkDefragmentedHeap {
54     // Since we store all the small objects, they become old and old regions are also allocated at the bottom of the heap
55     // together with humongous regions. So if there are a lot of old regions in the lower part of the heap,
56     // the humongous regions will be allocated in the upper part of the heap anyway.
57     // To avoid this the Eden needs to be big enough to fit all the small objects.
58     private static final int INITIAL_HEAP_SIZE  = 200 * 1024 * 1024;
59     private static final int MINIMAL_YOUNG_SIZE = 190 * 1024 * 1024;
60     private static final int MAXIMUM_HEAP_SIZE  = 256 * 1024 * 1024;
61     private static final int REGION_SIZE        = 1 * 1024 * 1024;
62 
main(String[] args)63     public static void main(String[] args) throws Exception, Throwable {
64         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
65                 "-XX:InitialHeapSize=" + INITIAL_HEAP_SIZE,
66                 "-Xmn" + MINIMAL_YOUNG_SIZE,
67                 "-Xmx" + MAXIMUM_HEAP_SIZE,
68                 "-XX:MinHeapFreeRatio=10",
69                 "-XX:MaxHeapFreeRatio=11",
70                 "-XX:+UseG1GC",
71                 "-XX:G1HeapRegionSize=" + REGION_SIZE,
72                 "-XX:-ExplicitGCInvokesConcurrent",
73                 "-verbose:gc",
74                 GCTest.class.getName()
75         );
76 
77         OutputAnalyzer output = ProcessTools.executeProcess(pb);
78         output.shouldHaveExitValue(0);
79     }
80 
81     static class GCTest {
82 
83         private static final String MIN_FREE_RATIO_FLAG_NAME = "MinHeapFreeRatio";
84         private static final String MAX_FREE_RATIO_FLAG_NAME = "MaxHeapFreeRatio";
85         private static final String NEW_SIZE_FLAG_NAME = "NewSize";
86 
87         private static final ArrayList<ArrayList<byte[]>> garbage = new ArrayList<>();
88 
89         private static final int SMALL_OBJS_SIZE  = 10 * 1024; // 10kB
90         private static final int SMALL_OBJS_COUNT = MINIMAL_YOUNG_SIZE / (SMALL_OBJS_SIZE-1);
91         private static final int ALLOCATE_COUNT = 3;
92         // try to put all humongous object into gap between min young size and initial heap size
93         // to avoid implicit GCs
94         private static final int HUMONG_OBJS_SIZE = (int) Math.max(
95                 (INITIAL_HEAP_SIZE - MINIMAL_YOUNG_SIZE) / ALLOCATE_COUNT / 4,
96                 REGION_SIZE * 1.1
97         );
98 
99         private static final long initialHeapSize = getHeapMemoryUsage().getUsed();
100 
main(String[] args)101         public static void main(String[] args) throws InterruptedException {
102             new GCTest().test();
103         }
104 
test()105         private void test() throws InterruptedException {
106             MemoryUsagePrinter.printMemoryUsage("init");
107 
108             allocate();
109             System.gc();
110             MemoryUsage muFull = getHeapMemoryUsage();
111             MemoryUsagePrinter.printMemoryUsage("allocated");
112 
113             free();
114             //Thread.sleep(1000); // sleep before measures due lags in JMX
115             MemoryUsage muFree = getHeapMemoryUsage();
116             MemoryUsagePrinter.printMemoryUsage("free");
117 
118             assertLessThan(muFree.getCommitted(), muFull.getCommitted(), prepareMessageCommittedIsNotLess() );
119         }
120 
allocate()121         private void allocate() {
122             System.out.format("Will allocate objects of small size = %s and humongous size = %s",
123                     MemoryUsagePrinter.NF.format(SMALL_OBJS_SIZE),
124                     MemoryUsagePrinter.NF.format(HUMONG_OBJS_SIZE)
125             );
126 
127             for (int i = 0; i < ALLOCATE_COUNT; i++) {
128                 ArrayList<byte[]> stuff = new ArrayList<>();
129                 allocateList(stuff, SMALL_OBJS_COUNT / ALLOCATE_COUNT, SMALL_OBJS_SIZE);
130                 garbage.add(stuff);
131 
132                 ArrayList<byte[]> humongousStuff = new ArrayList<>();
133                 allocateList(humongousStuff, 4, HUMONG_OBJS_SIZE);
134                 garbage.add(humongousStuff);
135             }
136         }
137 
free()138         private void free() {
139             // do not free last one list
140             garbage.subList(0, garbage.size() - 1).clear();
141 
142             // do not free last one element from last list
143             ArrayList<byte[]> stuff = garbage.get(garbage.size() - 1);
144             if (stuff.size() > 1) {
145                 stuff.subList(0, stuff.size() - 1).clear();
146             }
147             System.gc();
148         }
149 
prepareMessageCommittedIsNotLess()150         private String prepareMessageCommittedIsNotLess() {
151             return String.format(
152                     "committed free heap size is not less than committed full heap size, heap hasn't been shrunk?%n"
153                     + "%s = %s%n%s = %s",
154                     MIN_FREE_RATIO_FLAG_NAME,
155                     ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
156                         .getVMOption(MIN_FREE_RATIO_FLAG_NAME).getValue(),
157                     MAX_FREE_RATIO_FLAG_NAME,
158                     ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
159                         .getVMOption(MAX_FREE_RATIO_FLAG_NAME).getValue()
160             );
161         }
162 
allocateList(List<byte[]> garbage, int count, int size)163         private static void allocateList(List<byte[]> garbage, int count, int size) {
164             for (int i = 0; i < count; i++) {
165                 garbage.add(new byte[size]);
166             }
167         }
168     }
169 
getHeapMemoryUsage()170     static MemoryUsage getHeapMemoryUsage() {
171         return ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
172     }
173 
174     /**
175      * Prints memory usage to standard output
176      */
177     static class MemoryUsagePrinter {
178 
179         public static final NumberFormat NF = Helpers.numberFormatter();
180 
printMemoryUsage(String label)181         public static void printMemoryUsage(String label) {
182             MemoryUsage memusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
183             float freeratio = 1f - (float) memusage.getUsed() / memusage.getCommitted();
184             System.out.format("[%-24s] init: %-7s, used: %-7s, comm: %-7s, freeRatio ~= %.1f%%%n",
185                     label,
186                     NF.format(memusage.getInit()),
187                     NF.format(memusage.getUsed()),
188                     NF.format(memusage.getCommitted()),
189                     freeratio * 100
190             );
191         }
192     }
193 }
194