blob: 2a2d9161cb011a4111aa6732a8eb172b882524d5 [file] [log] [blame]
/*
* 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);
}
}