/* * Copyright (c) 2010, 2012, 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 com.sun.max.vm.heap.gcx; import static com.sun.max.vm.heap.gcx.HeapRegionConstants.*; import com.sun.max.annotate.*; import com.sun.max.memory.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.heap.*; import com.sun.max.vm.heap.gcx.rset.*; import com.sun.max.vm.layout.*; import com.sun.max.vm.reference.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.type.*; /** * The Heap Region Manager organize heap memory into fixed-size regions. * It provides an interface to create multiple "heap accounts", each with a guaranteed * reserve of space (an integral number of regions). Heaps allocate space from their * heap accounts, return free space to it, and may grow or shrink their accounts. * The heap region manager may also request a heap account to trade or free some specific * regions. */ public final class HeapRegionManager implements HeapAccountOwner { /** * The single instance of the heap region manager. */ static final HeapRegionManager theHeapRegionManager = new HeapRegionManager(); /** * Region allocator used by the heap manager. */ private final FixedSizeRegionAllocator regionAllocator; FixedSizeRegionAllocator regionAllocator() { return regionAllocator; } /** * Heap account serving the needs of the heap region manager. */ private HeapAccount<HeapRegionManager> managerHeapAccount; /** * Heap account of the region manager. This account is distinct from all other heap accounts * and in particular, heap accounts used by applications. It serves the need of the heap region manager only. */ public HeapAccount<HeapRegionManager> heapAccount() { return managerHeapAccount; } /** * Size of the space reserved by the heap region manager. * @return a size in byte */ public Size size() { return Size.fromInt(heapAccount().reserve()).shiftedLeft(log2RegionSizeInBytes); } /** * The allocator used by the HeapRegionManager to allocate its own objects. * A simple atomic bump allocator for now. */ final AtomicBumpPointerAllocator<Refiller> managerAllocator; /** * Return the address of the HeapRegionManager's own allocator. * This is the allocator used to allocate the HeapRegionManager's objects at startup. * The address as an allocator identifier for custom allocation. * @see HeapScheme#enableCustomAllocation(Address) * * @return an address that can be used for setting up custom allocation */ public Address allocator() { return Reference.fromJava(managerAllocator).toOrigin(); } /** * Total number of unreserved regions. */ private int unreserved; /** * */ private OutgoingReferenceChecker outgoingReferenceChecker; // Region reservation management interface. Should be private to heap account. // May want to revisit how the two interacts to better control // use of these sensitive operations (ideally, the transfer of unreserved to a // heap account region should be atomic. /** * Reserve the specified number of regions. * * @param numRegions number of region requested * @return true if the number of regions requested was reserved */ boolean reserve(int numRegions) { if (numRegions > unreserved) { return false; } unreserved -= numRegions; return true; } /** * Release reserved regions (i.e., "unreserved" them). * * @param numRegions */ void release(int numRegions) { FatalError.check((unreserved + numRegions) <= regionAllocator.capacity(), "invalid request"); unreserved += numRegions; } public boolean contains(Address address) { return regionAllocator.contains(address); } public MemoryRegion bounds() { return regionAllocator.bounds(); } boolean isValidRegionID(int regionID) { return regionAllocator.isValidRegionId(regionID); } private HeapRegionManager() { regionAllocator = new FixedSizeRegionAllocator("Heap Backing Storage"); managerHeapAccount = new HeapAccount<HeapRegionManager>(this); managerAllocator = new AtomicBumpPointerAllocator<Refiller>(new Refiller() { @Override public Address allocateRefill(Size requestedSize, Pointer startOfSpaceLeft, Size spaceLeft) { FatalError.unexpected("Must not have to refill"); return Address.zero(); } @Override protected void doBeforeGC() { } @Override public Address allocateLargeRaw(Size size) { FatalError.unexpected("Must never handle a large request"); return Address.zero(); } }); } private Size tupleSize(Class tupleClass) { return ClassActor.fromJava(tupleClass).dynamicTupleSize(); } @INLINE public static HeapRegionManager theHeapRegionManager() { return theHeapRegionManager; } /** * Initialize the region manager with enough regions to satisfy a specified maximum of heap space. * The total number of regions should covers both the specified maximum heap space and the needs of the region manager. * * The region manager is provided with a contiguous range of virtual memory that should be large enough to cover the space needed both for the heap space and * the region manager's book-keeping data structure. * After initialization succeeds, the effective amount of space managed by the region manager can be obtained by invoking {@link #bounds()}. * The space left over from the supplied reserved space is uncommitted. * TODO: currently, the space is provided committed, and the heap manager is responsible for uncommitting what it doesn't use. It should be the other way around: * provide uncommitted space and the region manager commits what it needs. * * TODO: we need a better way to capture the extra space requirements a heap scheme needs for the boot heap account. * Or may be we just need to add an additional VM/GC heap account, so that the boot heap account is strictly for the heap region manager * while the GC (or each GC) create it's own. * * @param reservedSpace start of the virtual memory reserved for the managed space * @param endOfReservedSpace end of the reserved space * @param heapSpaceSize size in byte of the heap space * @param regionInfoClass the sub-class of HeapRegionInfo used for region management. * */ public void initialize(Address reservedSpace, Address endOfReservedSpace, Size heapSpaceSize, Class<HeapRegionInfo> regionInfoClass, int bootTag) { // Initialize region constants (size and log constants). // The size of regions is computed from the requested heap size so as to keep the region table bounded and adapt region size to the heap size // (in particular, very large heap command large region size). HeapRegionConstants.initializeConstants(heapSpaceSize); // Adjust reserved space to region boundaries. final Address startOfManagedSpace = reservedSpace.alignUp(regionSizeInBytes); final Address endOfManagedSpace = startOfManagedSpace.plus(heapSpaceSize).alignUp(regionSizeInBytes); final int numHeapRegions = endOfManagedSpace.minus(startOfManagedSpace).asSize().unsignedShiftedRight(log2RegionSizeInBytes).toInt(); // Always count 10K of extra space for the odd objects // (e.g., the OutgoingReferenceChecker instance, the MemoryRegion [] created by the var args of InspectableHeapInfo, etc... final Size extraSpace = Size.K.times(10); int numExtraBootRegions = extraSpace.alignUp(regionSizeInBytes).unsignedShiftedRight(log2RegionSizeInBytes).toInt(); // We must add to this number of regions the regions to cover the space needed for the boot heap which allocate the region manager's data. // Per region book-keeping space: region descriptor plus links in region lists (2 links per region per list, two lists -- ownership and accounting). int perRegionSpaceRequirement = tupleSize(regionInfoClass).toInt() + 4 * Kind.INT.width.numberOfBytes; int numRegionsPerBootRegion = regionSizeInBytes / perRegionSpaceRequirement; int numTotalRegions = numHeapRegions + numExtraBootRegions; int numBootKeepingRegions = (numTotalRegions * perRegionSpaceRequirement) / regionSizeInBytes; while (numBootKeepingRegions > numRegionsPerBootRegion) { numTotalRegions += numBootKeepingRegions; numBootKeepingRegions = 1 + (numBootKeepingRegions * perRegionSpaceRequirement) / regionSizeInBytes; } numTotalRegions += numBootKeepingRegions; // Final count of space needed for the VM startup heap: add the Empty region table plus empty region lists plus 1 final Size bootHeapSize = extraSpace.plus(tupleSize(RegionTable.class).plus(Layout.getArraySize(Kind.INT, 0).times(2)).plus(perRegionSpaceRequirement * numTotalRegions)).alignUp(regionSizeInBytes); final Size managedSpaceSize = Size.fromInt(numTotalRegions).shiftedLeft(log2RegionSizeInBytes); FatalError.check(startOfManagedSpace.plus(managedSpaceSize).lessEqual(endOfReservedSpace), "Not enough reserved space to initialize managed space"); // Estimate conservatively how much space the heap manager needs initially. This is to commit // enough memory to get started. final int initialNumRegions = bootHeapSize.unsignedShiftedRight(log2RegionSizeInBytes).toInt(); if (MaxineVM.isDebug()) { Log.print("Initialize heap region manager's boot allocator with "); Log.print(initialNumRegions); Log.print(" regions ("); Log.printToPowerOfTwoUnits(bootHeapSize); Log.println(" bytes)"); } unreserved = numTotalRegions; final HeapScheme heapScheme = VMConfiguration.vmConfig().heapScheme(); if (heapScheme instanceof RSetCoverage) { ((RSetCoverage) heapScheme).initializeCoverage(startOfManagedSpace, managedSpaceSize); } // initialize the bootstrap allocator. The rest of the initialization code needs to allocate heap region management // object. We solve the bootstrapping problem this causes by using a linear allocator as a custom allocator for the current // thread. The contiguous set of regions consumed by the initialization will be accounted after the fact to the special // boot heap account. managerAllocator.initialize(startOfManagedSpace, bootHeapSize, bootHeapSize); try { heapScheme.enableCustomAllocation(Reference.fromJava(managerAllocator).toOrigin()); // Record initial space usage. regionAllocator.initialize(startOfManagedSpace, numTotalRegions, initialNumRegions); RegionTable.initialize(regionInfoClass, regionAllocator.bounds(), numTotalRegions); // Allocate the backing storage for the region lists. HeapRegionList.initializeListStorage(numTotalRegions); FatalError.check(managerAllocator.end().roundedUpBy(regionSizeInBytes).lessEqual(startOfManagedSpace.plus(bootHeapSize)), ""); // Ready to open bootstrap heap accounts now. // Start with opening the boot heap account to set the records straight after bootstrap. // TODO (ld) initialNumRegions may not be the reserve we want here. Need to adjust that to the desired "immortal" size. FatalError.check(managerHeapAccount.open(initialNumRegions), "Failed to create boot heap account"); if (MaxineVM.isDebug()) { outgoingReferenceChecker = new OutgoingReferenceChecker(managerHeapAccount); } // Now fix up the boot heap account to records the regions used up to now. // They are recorded committed HeapAccount.completeBootHeapAccountBootstrap(initialNumRegions, bootTag); // First, uncommit reserved space after the fact. All the space between the end of the initial boot heap and the end of the provided reserved space should be uncommitted. if (!Heap.AvoidsAnonOperations) { final Address endOfInitialBootHeap = startOfManagedSpace.plus(bootHeapSize); final Address endOfRegions = bounds().end(); Size uncommitedSpaceSize = endOfRegions.minus(endOfInitialBootHeap).asSize(); if (!VirtualMemory.uncommitMemory(endOfInitialBootHeap, uncommitedSpaceSize, VirtualMemory.Type.DATA)) { MaxineVM.reportPristineMemoryFailure("uncommitted regions", "uncommit", uncommitedSpaceSize); } } } finally { VMConfiguration.vmConfig().heapScheme().disableCustomAllocation(); } } /** * Request a number of contiguous regions. * @param numRegions * @return the identifier of the first region of the contiguous range allocated or {@link HeapRegionConstants#INVALID_REGION_ID} if the * request cannot be satisfied. */ int allocate(int numRegions) { return regionAllocator.allocate(numRegions); } /** * Request a number of regions. The allocated regions are added at the head or tail of the list depending on the value * specified in the append parameter. The allocate does a best effort to provides contiguous regions. * * @param list list where the allocated regions are recorded * @param numRegions number of regions requested * @param append Append the allocated region to the list if true, otherwise, prepend it. * @param exact if true, fail if the number of requested regions cannot be satisfied, otherwise allocate * as many regions as possible * @return the number of regions allocated */ int allocate(HeapRegionList list, int numRegions, boolean append, boolean exact) { FatalError.unimplemented(); return 0; } /** * Free contiguous regions. * @param firstRegionId identifier of the first region * @param numRegions */ void free(int firstRegionId, int numRegions) { // TODO(ld): error handling regionAllocator.free(firstRegionId, numRegions); } void commit(int firstRegionId, int numRegions) { // TODO(ld): error handling regionAllocator.commit(firstRegionId, numRegions); } void uncommit(int firstRegionId, int numRegions) { // TODO:(ld) error handling regionAllocator.uncommit(firstRegionId, numRegions); } /** * Verifies, in debug mode only (@see {@link MaxineVM#isDebug()}), that no references from this heap region manager's heap account escape. */ public void checkOutgoingReferences() { if (MaxineVM.isDebug()) { managerAllocator.unsafeMakeParsable(); managerHeapAccount.visitObjects(outgoingReferenceChecker); if (outgoingReferenceChecker.outgoingReferenceCount() != 0L) { final boolean lockDisabledSafepoints = Log.lock(); Log.print("Boot heap account has "); Log.print(outgoingReferenceChecker.outgoingReferenceCount()); Log.println(" outgoing references."); Log.unlock(lockDisabledSafepoints); FatalError.crash("Must not happen"); } } } public boolean supportsTagging() { return false; } public boolean isGcThread(Thread thread) { return false; } }