/* * Copyright (c) 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. * */ package gc.g1.humongousObjects; import gc.testlibrary.Helpers; import jdk.test.lib.Asserts; import sun.hotspot.WhiteBox; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @test TestHeapCounters * @summary Checks that heap counters work as expected after humongous allocations/deallocations * @requires vm.gc=="G1" | vm.gc=="null" * @library /testlibrary /test/lib / * @modules java.management * @build sun.hotspot.WhiteBox * gc.testlibrary.Helpers * gc.g1.humongousObjects.TestHeapCounters * @run driver ClassFileInstaller sun.hotspot.WhiteBox * sun.hotspot.WhiteBox$WhiteBoxPermission * * @run main/othervm -XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. * -Xmx128m -Xms128m * -XX:G1HeapRegionSize=1M -XX:InitiatingHeapOccupancyPercent=100 -XX:-G1UseAdaptiveIHOP * -Xlog:gc -Xlog:gc:file=TestHeapCountersRuntime.gc.log * gc.g1.humongousObjects.TestHeapCounters RUNTIME_COUNTER * * @run main/othervm -XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. * -Xmx128m -Xms128m * -XX:G1HeapRegionSize=1M -XX:InitiatingHeapOccupancyPercent=100 -XX:-G1UseAdaptiveIHOP * -Xlog:gc -Xlog:gc:file=TestHeapCountersMXBean.gc.log * gc.g1.humongousObjects.TestHeapCounters MX_BEAN_COUNTER */ public class TestHeapCounters { private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); private static final int G1_REGION_SIZE = WHITE_BOX.g1RegionSize(); private static final int HALF_G1_REGION_SIZE = G1_REGION_SIZE / 2; // Since during deallocation GC could free (very unlikely) some non-humongous date this value relaxes amount of // memory we expect to be freed. private static final double ALLOCATION_SIZE_TOLERANCE_FACTOR = 0.85D; private enum MemoryCounter { MX_BEAN_COUNTER { @Override public long getUsedMemory() { return ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); } }, RUNTIME_COUNTER { @Override public long getUsedMemory() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } }; public abstract long getUsedMemory(); } private static class Allocation { private byte[] allocation; public final long expectedSize; public Allocation(int allocationSize, long allocationExpectedSize) { allocation = new byte[allocationSize]; expectedSize = allocationExpectedSize; System.out.println(String.format("Object size is %d; Object is %shumongous", WHITE_BOX.getObjectSize(allocation), (WHITE_BOX.g1IsHumongous(allocation) ? "" : "non-"))); selfTest(); } private void selfTest() { boolean isHumongous = WHITE_BOX.getObjectSize(allocation) > HALF_G1_REGION_SIZE; boolean shouldBeHumongous = WHITE_BOX.g1IsHumongous(allocation); // Sanity check Asserts.assertEquals(isHumongous, shouldBeHumongous, String.format("Test Bug: Object of size %d is expected to be %shumongous but it is not", WHITE_BOX.getObjectSize(allocation), (shouldBeHumongous ? "" : "non-"))); } public void forgetAllocation() { allocation = null; } } public static void main(String[] args) { if (args.length != 1) { throw new Error("Expected memory counter name wasn't provided as command line argument"); } MemoryCounter memoryCounter = MemoryCounter.valueOf(args[0].toUpperCase()); int byteArrayMemoryOverhead = Helpers.detectByteArrayAllocationOverhead(); // Largest non-humongous byte[] int maxByteArrayNonHumongousSize = HALF_G1_REGION_SIZE - byteArrayMemoryOverhead; // Maximum byte[] that takes one region int maxByteArrayOneRegionSize = G1_REGION_SIZE - byteArrayMemoryOverhead; List allocationSizes = Arrays.asList( (int) maxByteArrayNonHumongousSize + 1, (int) (0.8f * maxByteArrayOneRegionSize), (int) (maxByteArrayOneRegionSize), (int) (1.2f * maxByteArrayOneRegionSize), (int) (1.5f * maxByteArrayOneRegionSize), (int) (1.7f * maxByteArrayOneRegionSize), (int) (2.0f * maxByteArrayOneRegionSize), (int) (2.5f * maxByteArrayOneRegionSize) ); List allocations = new ArrayList<>(); List gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); long gcCountBefore = gcBeans.stream().mapToLong(GarbageCollectorMXBean::getCollectionCount).sum(); System.out.println("Starting allocations - no GC should happened until we finish them"); allocationSizes.stream().forEach(allocationSize -> { long usedMemoryBefore = memoryCounter.getUsedMemory(); long expectedAllocationSize = (long) Math.ceil((double) allocationSize / G1_REGION_SIZE) * G1_REGION_SIZE; allocations.add(new Allocation(allocationSize, expectedAllocationSize)); long usedMemoryAfter = memoryCounter.getUsedMemory(); System.out.println("Counter returned that allocation used " + (usedMemoryAfter - usedMemoryBefore) + "; " + "expected allocation size at least " + expectedAllocationSize * ALLOCATION_SIZE_TOLERANCE_FACTOR); long gcCountNow = gcBeans.stream().mapToLong(GarbageCollectorMXBean::getCollectionCount).sum(); if (gcCountNow == gcCountBefore) { // We should allocate at least allocation.expectedSize Asserts.assertGreaterThanOrEqual(usedMemoryAfter - usedMemoryBefore, expectedAllocationSize, "Counter of type " + memoryCounter.getClass().getSimpleName() + " returned wrong allocation size"); } else { System.out.println("GC happened during allocation so the check is skipped"); } }); System.out.println("Finished allocations - no GC should happened before this line"); allocations.stream().forEach(allocation -> { long usedMemoryBefore = memoryCounter.getUsedMemory(); allocation.forgetAllocation(); WHITE_BOX.fullGC(); long usedMemoryAfter = memoryCounter.getUsedMemory(); // We should free at least allocation.expectedSize * ALLOCATION_SIZE_TOLERANCE_FACTOR Asserts.assertGreaterThanOrEqual(usedMemoryBefore - usedMemoryAfter, (long) (allocation.expectedSize * ALLOCATION_SIZE_TOLERANCE_FACTOR), "Counter of type " + memoryCounter.getClass().getSimpleName() + " returned wrong allocation size"); }); } }