/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.mmtk.policy; import org.mmtk.plan.Plan; import org.mmtk.plan.TransitiveClosure; import org.mmtk.utility.heap.Map; import org.mmtk.utility.heap.Mmapper; import org.mmtk.utility.heap.PageResource; import org.mmtk.utility.heap.SpaceDescriptor; import org.mmtk.utility.heap.VMRequest; import org.mmtk.utility.options.Options; import org.mmtk.utility.Log; import org.mmtk.utility.Constants; import org.mmtk.vm.VM; import org.vmmagic.pragma.*; import org.vmmagic.unboxed.*; /** * This class defines and manages spaces. Each policy is an instance * of a space. A space is a region of virtual memory (contiguous or * discontigous) which is subject to the same memory management * regime. Multiple spaces (instances of this class or its * descendants) may have the same policy (eg there could be numerous * instances of CopySpace, each with different roles). Spaces are * defined in terms of a unique region of virtual memory, so no two * space instances ever share any virtual memory.<p> * * In addition to tracking virtual memory use and the mapping to * policy, spaces also manage memory consumption (<i>used</i> virtual * memory).<p> * */ @Uninterruptible public abstract class Space implements Constants { /**************************************************************************** * * Class variables */ private static boolean DEBUG = false; // the following is somewhat arbitrary for the 64 bit system at this stage public static final int LOG_ADDRESS_SPACE = (BYTES_IN_ADDRESS == 4) ? 32 : 40; public static final int LOG_BYTES_IN_CHUNK = 22; public static final int BYTES_IN_CHUNK = 1 << LOG_BYTES_IN_CHUNK; public static final int PAGES_IN_CHUNK = 1 << (LOG_BYTES_IN_CHUNK - LOG_BYTES_IN_PAGE); private static final int LOG_MAX_CHUNKS = LOG_ADDRESS_SPACE - LOG_BYTES_IN_CHUNK; public static final int MAX_CHUNKS = 1 << LOG_MAX_CHUNKS; public static final int MAX_SPACES = 20; // quite arbitrary public static final Address HEAP_START = chunkAlign(VM.HEAP_START, true); public static final Address AVAILABLE_START = chunkAlign(VM.AVAILABLE_START, false); public static final Address AVAILABLE_END = chunkAlign(VM.AVAILABLE_END, true); public static final Extent AVAILABLE_BYTES = AVAILABLE_END.toWord().minus(AVAILABLE_START.toWord()).toExtent(); public static final int AVAILABLE_PAGES = AVAILABLE_BYTES.toWord().rshl(LOG_BYTES_IN_PAGE).toInt(); public static final Address HEAP_END = chunkAlign(VM.HEAP_END, false); private static final boolean FORCE_SLOW_MAP_LOOKUP = false; private static final int PAGES = 0; private static final int MB = 1; private static final int PAGES_MB = 2; private static final int MB_PAGES = 3; private static int spaceCount = 0; private static Space[] spaces = new Space[MAX_SPACES]; private static Address heapCursor = HEAP_START; private static Address heapLimit = HEAP_END; /**************************************************************************** * * Instance variables */ private final String name; private final int nameLength; protected final int descriptor; private final int index; private final VMRequest vmRequest; protected final boolean immortal; protected final boolean movable; protected final boolean contiguous; protected PageResource pr; protected final Address start; protected final Extent extent; protected Address headDiscontiguousRegion; private boolean allocationFailed; /**************************************************************************** * * Initialization */ { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(PAGES_IN_CHUNK > 1); } /** * This is the base constructor for <i>all</i> spaces.<p> * * @param name The name of this space (used when printing error messages etc) * @param movable Are objects in this space movable? * @param immortal Are objects in this space immortal (uncollected)? * @param vmRequest An object describing the virtual memory requested. */ protected Space(String name, boolean movable, boolean immortal, VMRequest vmRequest) { this.name = name; this.nameLength = name.length(); // necessary to avoid calling length() in uninterruptible code this.movable = movable; this.immortal = immortal; this.vmRequest = vmRequest; this.index = spaceCount++; spaces[index] = this; if (vmRequest.type == VMRequest.REQUEST_DISCONTIGUOUS) { this.contiguous = false; this.descriptor = SpaceDescriptor.createDescriptor(); this.start = Address.zero(); this.extent = Extent.zero(); this.headDiscontiguousRegion = Address.zero(); VM.memory.setHeapRange(index, HEAP_START, HEAP_END); // this should really be refined! Once we have a code space, we can be a lot more specific about what is a valid code heap area return; } Address start; Extent extent; if (vmRequest.type == VMRequest.REQUEST_FRACTION) { extent = getFracAvailable(vmRequest.frac); } else { extent = vmRequest.extent; } if (extent.NE(chunkAlign(extent, false))) { VM.assertions.fail(name + " requested non-aligned extent: " + extent.toLong() + " bytes"); } if (vmRequest.type == VMRequest.REQUEST_FIXED) { start = vmRequest.start; if (start.NE(chunkAlign(start, false))) { VM.assertions.fail(name + " starting on non-aligned boundary: " + start.toLong() + " bytes"); } } else if (vmRequest.top) { heapLimit = heapLimit.minus(extent); start = heapLimit; } else { start = heapCursor; heapCursor = heapCursor.plus(extent); } if (heapCursor.GT(heapLimit)) { Log.write("Out of virtual address space allocating \""); Log.write(name); Log.write("\" at "); Log.write(heapCursor.minus(extent)); Log.write(" ("); Log.write(heapCursor); Log.write(" > "); Log.write(heapLimit); Log.writeln(")"); VM.assertions.fail("exiting"); } this.contiguous = true; this.start = start; this.extent = extent; this.descriptor = SpaceDescriptor.createDescriptor(start, start.plus(extent)); VM.memory.setHeapRange(index, start, start.plus(extent)); Map.insert(start, extent, descriptor, this); if (DEBUG) { Log.write(name); Log.write(" "); Log.write(start); Log.write(" "); Log.write(start.plus(extent)); Log.write(" "); Log.writeln(extent.toWord()); } } /**************************************************************************** * * Accessor methods */ /** Start of discontig getter @return The start of the discontiguous space */ public static Address getDiscontigStart() { return heapCursor; } /** End of discontig getter @return The end of the discontiguous space */ public static Address getDiscontigEnd() { return heapLimit.minus(1); } /** Name getter @return The name of this space */ public final String getName() { return name; } /** Start getter @return The start address of this space */ public final Address getStart() { return start; } /** Extent getter @return The size (extent) of this space */ public final Extent getExtent() { return extent; } /** Descriptor method @return The integer descriptor for this space */ public final int getDescriptor() { return descriptor; } /** Index getter @return The index (ordinal number) of this space */ public final int getIndex() { return index; } /** Immortal getter @return True if this space is never collected */ public final boolean isImmortal() { return immortal; } /** Movable getter @return True if objects in this space may move */ public boolean isMovable() { return movable; } /** Allocationfailed getter @return true if an allocation has failed since GC */ public final boolean allocationFailed() { return allocationFailed; } /** Clear Allocationfailed flag */ public final void clearAllocationFailed() { allocationFailed = false; } /** ReservedPages getter @return The number of reserved pages */ public final int reservedPages() { return pr.reservedPages(); } /** CommittedPages getter @return The number of committed pages */ public final int committedPages() { return pr.committedPages(); } /** RequiredPages getter @return The number of required pages */ public final int requiredPages() { return pr.requiredPages(); } /** AvailablePages getter @return The number of pages available for allocation */ public final int availablePhysicalPages() { return pr.getAvailablePhysicalPages(); } /** Cumulative committed pages getter @return Cumulative committed pages. */ public static long cumulativeCommittedPages() { return PageResource.cumulativeCommittedPages(); } /**************************************************************************** * * Object and address tests / accessors */ /** * Return true if the given object is in an immortal (uncollected) space. * * @param object The object in question * @return True if the given object is in an immortal (uncollected) space. */ public static boolean isImmortal(ObjectReference object) { Space space = getSpaceForObject(object); if (space == null) return true; else return space.isImmortal(); } /** * Return true if the given object is in space that moves objects. * * @param object The object in question * @return True if the given object is in space that moves objects. */ @Inline public static boolean isMovable(ObjectReference object) { Space space = getSpaceForObject(object); if (space == null) return true; else return space.isMovable(); } /** * Return true if the given object is in a space managed by MMTk. * * @param object The object in question * @return True if the given object is in a space managed by MMTk. */ @Inline public static boolean isMappedObject(ObjectReference object) { return !object.isNull() && (getSpaceForObject(object) != null) && Mmapper.objectIsMapped(object); } /** * Return true if the given address is in a space managed by MMTk. * * @param address The address in question * @return True if the given address is in a space managed by MMTk. */ @Inline public static boolean isMappedAddress(Address address) { return Map.getSpaceForAddress(address) != null && Mmapper.addressIsMapped(address); } /** * Return true if the given object is the space associated with the * given descriptor. * * @param descriptor The descriptor for a space * @param object The object in question * @return True if the given object is in the space associated with * the descriptor. */ @Inline public static boolean isInSpace(int descriptor, ObjectReference object) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!object.isNull()); return isInSpace(descriptor, VM.objectModel.refToAddress(object)); } /** * Return true if the given address is the space associated with the * given descriptor. * * @param descriptor The descriptor for a space * @param address The address in question. * @return True if the given address is in the space associated with * the descriptor. */ @Inline public static boolean isInSpace(int descriptor, Address address) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!address.isZero()); if (FORCE_SLOW_MAP_LOOKUP || !SpaceDescriptor.isContiguous(descriptor)) { return Map.getDescriptorForAddress(address) == descriptor; } else { Address start = SpaceDescriptor.getStart(descriptor); if (!VM.VERIFY_ASSERTIONS && SpaceDescriptor.isContiguousHi(descriptor)) return address.GE(start); else { Extent size = Word.fromIntSignExtend(SpaceDescriptor.getChunks(descriptor)).lsh(LOG_BYTES_IN_CHUNK).toExtent(); Address end = start.plus(size); return address.GE(start) && address.LT(end); } } } /** * Return the space for a given object * * @param object The object in question * @return The space containing the object */ @Inline public static Space getSpaceForObject(ObjectReference object) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!object.isNull()); return Map.getSpaceForAddress(VM.objectModel.refToAddress(object)); } /** * Return the space for a given address, not necessarily the * start address of an object. * * @param addr The address in question * @return The space containing the address */ public static Space getSpaceForAddress(Address addr) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(!addr.isZero()); return Map.getSpaceForAddress(addr); } /**************************************************************************** * * Page management */ /** * Acquire a number of pages from the page resource, returning * either the address of the first page, or zero on failure.<p> * * This may trigger a GC if necessary.<p> * * First the page budget is checked to see whether polling the GC is * necessary. If so, the GC is polled. If a GC is required then the * request fails and zero is returned.<p> * * If the check of the page budget does not lead to GC being * triggered, then a request is made for specific pages in virtual * memory. If the page manager cannot satisify this request, then * the request fails, a GC is forced, and zero is returned. * Otherwise the address of the first page is returned.<p> * * @param pages The number of pages requested * @return The start of the first page if successful, zero on * failure. */ public final Address acquire(int pages) { boolean allowPoll = !Plan.gcInProgress() && Plan.isInitialized() && !VM.collection.isEmergencyAllocation(); /* First check page budget and poll if necessary */ if (!pr.reservePages(pages)) { /* Need to poll, either fixing budget or requiring GC */ if (allowPoll && VM.activePlan.global().poll(false, this)) { pr.clearRequest(pages); return Address.zero(); // GC required, return failure } } /* Page budget is ok, try to acquire virtual memory */ Address rtn = pr.getNewPages(pages); if (rtn.isZero()) { /* Failed, so force a GC */ if (VM.collection.isEmergencyAllocation()) { pr.clearRequest(pages); VM.assertions.fail("Failed emergency allocation"); } if (!allowPoll) VM.assertions.fail("Physical allocation failed during special (collection/emergency) allocation!"); allocationFailed = true; VM.collection.reportPhysicalAllocationFailed(); VM.activePlan.global().poll(true, this); pr.clearRequest(pages); return Address.zero(); } if (allowPoll) VM.collection.reportAllocationSuccess(); return rtn; } /** * Extend the virtual memory associated with a particular discontiguous * space. This simply involves requesting a suitable number of chunks * from the pool of chunks available to discontiguous spaces. * * @param chunks The number of chunks by which the space needs to be extended * @return The address of the new discontiguous space. */ public Address growDiscontiguousSpace(int chunks) { return headDiscontiguousRegion = Map.allocateContiguousChunks(descriptor, this, chunks, headDiscontiguousRegion); } /** * Return the number of chunks required to satisfy a request for a certain number of pages * * @param pages The number of pages desired * @return The number of chunks needed to satisfy the request */ public static int requiredChunks(int pages) { Extent extent = chunkAlign(Extent.fromIntZeroExtend(pages<<LOG_BYTES_IN_PAGE), false); return extent.toWord().rshl(LOG_BYTES_IN_CHUNK).toInt(); } /** * This hook is called by page resources each time a space grows. The space may * tap into the hook to monitor heap growth. The call is made from within the * page resources' critical region, immediately before yielding the lock. * * @param start The start of the newly allocated space * @param bytes The size of the newly allocated space * @param newChunk True if the new space encroached upon or started a new chunk or chunks. */ public void growSpace(Address start, Extent bytes, boolean newChunk) {} /** * Release one or more contiguous chunks associated with a discontiguous * space. * * @param chunk THe address of the start of the contiguous chunk or chunks * @return The number of chunks freed */ public int releaseDiscontiguousChunks(Address chunk) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(chunk.EQ(chunkAlign(chunk, true))); if (chunk.EQ(headDiscontiguousRegion)) { headDiscontiguousRegion = Map.getNextContiguousRegion(chunk); } return Map.freeContiguousChunks(chunk); } /** * Release a unit of allocation (a page or pages) * * @param start The address of the start of the region to be released */ public abstract void release(Address start); /** * Clear the allocation failed flag for all spaces. * */ public static void clearAllAllocationFailed() { for (int i = 0; i < spaceCount; i++) { spaces[i].clearAllocationFailed(); } } /** * Get the total number of pages reserved by all of the spaces * * @return the total number of pages reserved by all of the spaces */ private static int getPagesReserved() { int pages = 0; for (int i = 0; i < spaceCount; i++) { pages += spaces[i].reservedPages(); } return pages; } /**************************************************************************** * * Debugging / printing */ /** * Print out the memory used by all spaces, in megabytes */ public static void printUsageMB() { printUsage(MB); } /** * Print out the memory used by all spaces, in megabytes */ public static void printUsagePages() { printUsage(PAGES); } /** * Print out a map of virtual memory useage by all spaces */ public static void printVMMap() { Log.writeln("Key: (I)mmortal (N)onmoving (D)iscontiguous (E)xtent (F)raction"); Log.write(" HEAP_START "); Log.writeln(HEAP_START); Log.write("AVAILABLE_START "); Log.writeln(AVAILABLE_START); for (int i = 0; i < spaceCount; i++) { Space space = spaces[i]; for (int s = 0; s < 11 - space.nameLength; s++) Log.write(" "); Log.write(space.name); Log.write(" "); Log.write(space.immortal ? "I" : " "); Log.write(space.movable ? " " : "N"); if (space.contiguous) { Log.write(" "); Log.write(space.start); Log.write("->"); Log.write(space.start.plus(space.extent.minus(1))); if (space.vmRequest.type == VMRequest.REQUEST_EXTENT) { Log.write(" E "); Log.write(space.vmRequest.extent); } else if (space.vmRequest.type == VMRequest.REQUEST_FRACTION) { Log.write(" F "); Log.write(space.vmRequest.frac); } Log.writeln(); } else { Log.write("D ["); for(Address a = space.headDiscontiguousRegion; !a.isZero(); a = Map.getNextContiguousRegion(a)) { Log.write(a); Log.write("->"); Log.write(a.plus(Map.getContiguousRegionSize(a).minus(1))); if (Map.getNextContiguousRegion(a) != Address.zero()) Log.write(", "); } Log.writeln("]"); } } Log.write(" AVAILABLE_END "); Log.writeln(AVAILABLE_END); Log.write(" HEAP_END "); Log.writeln(HEAP_END); } /** * Interface to use to implement the Visitor Pattern for Spaces. */ public static interface SpaceVisitor { void visit(Space s); } /** * Implement the Visitor Pattern for Spaces. * @param v The visitor to perform on each Space instance */ @Interruptible public static void visitSpaces(SpaceVisitor v) { for (int i = 0; i < spaceCount; i++) { v.visit(spaces[i]); } } /** * Ensure that all MMTk spaces (all spaces aside from the VM space) * are mapped. Demand zero map all of them if they are not already * mapped. */ @Interruptible public static void eagerlyMmapMMTkSpaces() { eagerlyMmapMMTkContiguousSpaces(); eagerlyMmapMMTkDiscontiguousSpaces(); } /** * Ensure that all contiguous MMTk spaces are mapped. Demand zero map * all of them if they are not already mapped. */ @Interruptible public static void eagerlyMmapMMTkContiguousSpaces() { for (int i = 0; i < spaceCount; i++) { Space space = spaces[i]; if (space != VM.memory.getVMSpace()) { if (Options.verbose.getValue() > 2) { Log.write("Mapping "); Log.write(space.name); Log.write(" "); Log.write(space.start); Log.write("->"); Log.writeln(space.start.plus(space.extent.minus(1))); } Mmapper.ensureMapped(space.start, space.extent.toInt()>>LOG_BYTES_IN_PAGE); } } } /** * Ensure that all discontiguous MMTk spaces are mapped. Demand zero map * all of them if they are not already mapped. */ @Interruptible public static void eagerlyMmapMMTkDiscontiguousSpaces() { Address regionStart = Space.getDiscontigStart(); Address regionEnd = Space.getDiscontigEnd(); int pages = regionEnd.diff(regionStart).toInt()>>LOG_BYTES_IN_PAGE; Log.write("Mapping discontiguous spaces "); Log.write(regionStart); Log.write("->"); Log.writeln(regionEnd.minus(1)); Mmapper.ensureMapped(getDiscontigStart(), pages); } /** * Print out the memory used by all spaces in either megabytes or * pages. * * @param mode An enumeration type that specifies the format for the * prining (PAGES, MB, PAGES_MB, or MB_PAGES). */ private static void printUsage(int mode) { Log.write("used = "); printPages(getPagesReserved(), mode); boolean first = true; for (int i = 0; i < spaceCount; i++) { Space space = spaces[i]; Log.write(first ? " = " : " + "); first = false; Log.write(space.name); Log.write(" "); printPages(space.reservedPages(), mode); } Log.writeln(); } /** * Print out the number of pages and or megabytes, depending on the mode. * * @param pages The number of pages * @param mode An enumeration type that specifies the format for the * prining (PAGES, MB, PAGES_MB, or MB_PAGES). */ private static void printPages(int pages, int mode) { double mb = (double) (pages << LOG_BYTES_IN_PAGE) / (double) (1 << 20); switch (mode) { case PAGES: Log.write(pages); Log.write(" pgs"); break; case MB: Log.write(mb); Log.write(" Mb"); break; case PAGES_MB: Log.write(pages); Log.write(" pgs ("); Log.write(mb); Log.write(" Mb)"); break; case MB_PAGES: Log.write(mb); Log.write(" Mb ("); Log.write(pages); Log.write(" pgs)"); break; default: VM.assertions.fail("writePages passed illegal printing mode"); } } /**************************************************************************** * * Miscellaneous */ /** * Trace an object as part of a collection and return the object, * which may have been forwarded (if a copying collector). * * @param trace The trace being conducted. * @param object The object to trace * @return The object, forwarded, if appropriate */ public abstract ObjectReference traceObject(TransitiveClosure trace, ObjectReference object); /** * Has the object in this space been reached during the current collection. * This is used for GC Tracing. * * @param object The object reference. * @return True if the object is reachable. */ public boolean isReachable(ObjectReference object) { return isLive(object); } /** * Is the object in this space alive? * * @param object The object reference. * @return True if the object is live. */ public abstract boolean isLive(ObjectReference object); /** * Align an address to a space chunk * * @param addr The address to be aligned * @param down If true the address will be rounded down, otherwise * it will rounded up. * @return The chunk-aligned address */ public static Address chunkAlign(Address addr, boolean down) { if (!down) addr = addr.plus(BYTES_IN_CHUNK - 1); return addr.toWord().rshl(LOG_BYTES_IN_CHUNK).lsh(LOG_BYTES_IN_CHUNK).toAddress(); } /** * Align an extent to a space chunk * * @param bytes The extent to be aligned * @param down If true the extent will be rounded down, otherwise * it will rounded up. * @return The chunk-aligned extent */ public static Extent chunkAlign(Extent bytes, boolean down) { if (!down) bytes = bytes.plus(BYTES_IN_CHUNK - 1); return bytes.toWord().rshl(LOG_BYTES_IN_CHUNK).lsh(LOG_BYTES_IN_CHUNK).toExtent(); } /** * Convert a fraction into a number of bytes according to the * fraction of available bytes. * * @param frac The fraction of available virtual memory desired * @return The corresponding number of bytes, chunk-aligned. */ public static Extent getFracAvailable(float frac) { long bytes = (long) (frac * AVAILABLE_BYTES.toLong()); Word mb = Word.fromIntSignExtend((int) (bytes >> LOG_BYTES_IN_MBYTE)); Extent rtn = mb.lsh(LOG_BYTES_IN_MBYTE).toExtent(); return chunkAlign(rtn, false); } }