blob: 3145eb6385408460e0a55173c4279e90cd03a971 [file] [log] [blame]
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import com.oracle.java.testlibrary.Asserts;
import com.oracle.java.testlibrary.OutputAnalyzer;
import com.oracle.java.testlibrary.Platform;
import com.oracle.java.testlibrary.ProcessTools;
import com.oracle.java.testlibrary.Utils;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import sun.misc.Unsafe; // for ADDRESS_SIZE
import sun.hotspot.WhiteBox;
public class TestShrinkAuxiliaryData {
private static final int REGION_SIZE = 1024 * 1024;
private final static String[] initialOpts = new String[]{
"-XX:MinHeapFreeRatio=10",
"-XX:MaxHeapFreeRatio=11",
"-XX:+UseG1GC",
"-XX:G1HeapRegionSize=" + REGION_SIZE,
"-XX:-ExplicitGCInvokesConcurrent",
"-XX:+PrintGCDetails",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+WhiteBoxAPI",
"-Xbootclasspath/a:.",
};
private final int hotCardTableSize;
protected TestShrinkAuxiliaryData(int hotCardTableSize) {
this.hotCardTableSize = hotCardTableSize;
}
protected void test() throws Exception {
ArrayList<String> vmOpts = new ArrayList();
Collections.addAll(vmOpts, initialOpts);
int maxCacheSize = Math.max(0, Math.min(31, getMaxCacheSize()));
if (maxCacheSize < hotCardTableSize) {
System.out.format("Skiping test for %d cache size due max cache size %d",
hotCardTableSize, maxCacheSize
);
return;
}
printTestInfo(maxCacheSize);
vmOpts.add("-XX:G1ConcRSLogCacheSize=" + hotCardTableSize);
vmOpts.addAll(Arrays.asList(Utils.getTestJavaOpts()));
// for 32 bits ObjectAlignmentInBytes is not a option
if (Platform.is32bit()) {
ArrayList<String> vmOptsWithoutAlign = new ArrayList(vmOpts);
vmOptsWithoutAlign.add(ShrinkAuxiliaryDataTest.class.getName());
performTest(vmOptsWithoutAlign);
return;
}
for (int alignment = 3; alignment <= 8; alignment++) {
ArrayList<String> vmOptsWithAlign = new ArrayList(vmOpts);
vmOptsWithAlign.add("-XX:ObjectAlignmentInBytes="
+ (int) Math.pow(2, alignment));
vmOptsWithAlign.add(ShrinkAuxiliaryDataTest.class.getName());
performTest(vmOptsWithAlign);
}
}
private void performTest(List<String> opts) throws Exception {
ProcessBuilder pb
= ProcessTools.createJavaProcessBuilder(
opts.toArray(new String[opts.size()])
);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
System.out.println(output.getStdout());
System.err.println(output.getStderr());
output.shouldHaveExitValue(0);
}
private void printTestInfo(int maxCacheSize) {
DecimalFormat grouped = new DecimalFormat("000,000");
DecimalFormatSymbols formatSymbols = grouped.getDecimalFormatSymbols();
formatSymbols.setGroupingSeparator(' ');
grouped.setDecimalFormatSymbols(formatSymbols);
System.out.format(
"Test will use %s bytes of memory of %s available%n"
+ "Available memory is %s with %d bytes pointer size - can save %s pointers%n"
+ "Max cache size: 2^%d = %s elements%n",
grouped.format(ShrinkAuxiliaryDataTest.getMemoryUsedByTest()),
grouped.format(Runtime.getRuntime().maxMemory()),
grouped.format(Runtime.getRuntime().maxMemory()
- ShrinkAuxiliaryDataTest.getMemoryUsedByTest()),
Unsafe.ADDRESS_SIZE,
grouped.format((Runtime.getRuntime().freeMemory()
- ShrinkAuxiliaryDataTest.getMemoryUsedByTest())
/ Unsafe.ADDRESS_SIZE),
maxCacheSize,
grouped.format((int) Math.pow(2, maxCacheSize))
);
}
/**
* Detects maximum possible size of G1ConcRSLogCacheSize available for
* current process based on maximum available process memory size
*
* @return power of two
*/
private static int getMaxCacheSize() {
long availableMemory = Runtime.getRuntime().freeMemory()
- ShrinkAuxiliaryDataTest.getMemoryUsedByTest() - 1l;
if (availableMemory <= 0) {
return 0;
}
long availablePointersCount = availableMemory / Unsafe.ADDRESS_SIZE;
return (63 - (int) Long.numberOfLeadingZeros(availablePointersCount));
}
static class ShrinkAuxiliaryDataTest {
public static void main(String[] args) throws IOException {
ShrinkAuxiliaryDataTest testCase = new ShrinkAuxiliaryDataTest();
if (!testCase.checkEnvApplicability()) {
return;
}
testCase.test();
}
/**
* Checks is this environment suitable to run this test
* - memory is enough to decommit (page size is not big)
* - RSet cache size is not too big
*
* @return true if test could run, false if test should be skipped
*/
protected boolean checkEnvApplicability() {
int pageSize = WhiteBox.getWhiteBox().getVMPageSize();
System.out.println( "Page size = " + pageSize
+ " region size = " + REGION_SIZE
+ " aux data ~= " + (REGION_SIZE * 3 / 100));
// If auxdata size will be less than page size it wouldn't decommit.
// Auxiliary data size is about ~3.6% of heap size.
if (pageSize >= REGION_SIZE * 3 / 100) {
System.out.format("Skipping test for too large page size = %d",
pageSize
);
return false;
}
if (REGION_SIZE * REGIONS_TO_ALLOCATE > Runtime.getRuntime().maxMemory()) {
System.out.format("Skipping test for too low available memory. "
+ "Need %d, available %d",
REGION_SIZE * REGIONS_TO_ALLOCATE,
Runtime.getRuntime().maxMemory()
);
return false;
}
return true;
}
class GarbageObject {
private final List<byte[]> payload = new ArrayList();
private final List<GarbageObject> ref = new LinkedList();
public GarbageObject(int size) {
payload.add(new byte[size]);
}
public void addRef(GarbageObject g) {
ref.add(g);
}
public void mutate() {
if (!payload.isEmpty() && payload.get(0).length > 0) {
payload.get(0)[0] = (byte) (Math.random() * Byte.MAX_VALUE);
}
}
}
private final List<GarbageObject> garbage = new ArrayList();
public void test() throws IOException {
MemoryUsage muFull, muFree, muAuxDataFull, muAuxDataFree;
float auxFull, auxFree;
allocate();
link();
mutate();
muFull = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
long numUsedRegions = WhiteBox.getWhiteBox().g1NumMaxRegions()
- WhiteBox.getWhiteBox().g1NumFreeRegions();
muAuxDataFull = WhiteBox.getWhiteBox().g1AuxiliaryMemoryUsage();
auxFull = (float)muAuxDataFull.getUsed() / numUsedRegions;
System.out.format("Full aux data ratio= %f, regions max= %d, used= %d\n",
auxFull, WhiteBox.getWhiteBox().g1NumMaxRegions(), numUsedRegions
);
deallocate();
System.gc();
muFree = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
muAuxDataFree = WhiteBox.getWhiteBox().g1AuxiliaryMemoryUsage();
numUsedRegions = WhiteBox.getWhiteBox().g1NumMaxRegions()
- WhiteBox.getWhiteBox().g1NumFreeRegions();
auxFree = (float)muAuxDataFree.getUsed() / numUsedRegions;
System.out.format("Free aux data ratio= %f, regions max= %d, used= %d\n",
auxFree, WhiteBox.getWhiteBox().g1NumMaxRegions(), numUsedRegions
);
Asserts.assertLessThanOrEqual(muFree.getCommitted(), muFull.getCommitted(),
String.format("heap decommit failed - full > free: %d > %d",
muFree.getCommitted(), muFull.getCommitted()
)
);
System.out.format("State used committed\n");
System.out.format("Full aux data: %10d %10d\n", muAuxDataFull.getUsed(), muAuxDataFull.getCommitted());
System.out.format("Free aux data: %10d %10d\n", muAuxDataFree.getUsed(), muAuxDataFree.getCommitted());
// if decommited check that aux data has same ratio
if (muFree.getCommitted() < muFull.getCommitted()) {
Asserts.assertLessThanOrEqual(auxFree, auxFull,
String.format("auxiliary data decommit failed - full > free: %f > %f",
auxFree, auxFull
)
);
}
}
private void allocate() {
for (int r = 0; r < REGIONS_TO_ALLOCATE; r++) {
for (int i = 0; i < NUM_OBJECTS_PER_REGION; i++) {
GarbageObject g = new GarbageObject(REGION_SIZE
/ NUM_OBJECTS_PER_REGION);
garbage.add(g);
}
}
}
/**
* Iterate through all allocated objects, and link to objects in another
* regions
*/
private void link() {
for (int ig = 0; ig < garbage.size(); ig++) {
int regionNumber = ig / NUM_OBJECTS_PER_REGION;
for (int i = 0; i < NUM_LINKS; i++) {
int regionToLink;
do {
regionToLink = (int) (Math.random() * REGIONS_TO_ALLOCATE);
} while (regionToLink == regionNumber);
// get random garbage object from random region
garbage.get(ig).addRef(garbage.get(regionToLink
* NUM_OBJECTS_PER_REGION + (int) (Math.random()
* NUM_OBJECTS_PER_REGION)));
}
}
}
private void mutate() {
for (int ig = 0; ig < garbage.size(); ig++) {
garbage.get(ig).mutate();
}
}
private void deallocate() {
garbage.clear();
System.gc();
}
static long getMemoryUsedByTest() {
return REGIONS_TO_ALLOCATE * REGION_SIZE;
}
private static final int REGIONS_TO_ALLOCATE = 100;
private static final int NUM_OBJECTS_PER_REGION = 10;
private static final int NUM_LINKS = 20; // how many links create for each object
}
}