Exemplo n.º 1
0
 /**
  * Return the number of metadata pages reserved for use given the pending allocation.
  *
  * @return The number of pages reserved given the pending allocation, excluding space reserved for
  *     copying.
  */
 public int getMetaDataPagesUsed() {
   return metaDataSpace.reservedPages();
 }
Exemplo n.º 2
0
 /**
  * Return the number of pages reserved for use given the pending allocation.
  *
  * @return The number of pages reserved given the pending allocation, excluding space reserved for
  *     copying.
  */
 public int getPagesUsed() {
   return loSpace.reservedPages()
       + immortalSpace.reservedPages()
       + metaDataSpace.reservedPages()
       + nonMovingSpace.reservedPages();
 }
Exemplo n.º 3
0
 /**
  * Calculate the number of pages a collection is required to free to satisfy outstanding
  * allocation requests.
  *
  * @return the number of pages a collection is required to free to satisfy outstanding allocation
  *     requests.
  */
 public int getPagesRequired() {
   return loSpace.requiredPages()
       + metaDataSpace.requiredPages()
       + immortalSpace.requiredPages()
       + nonMovingSpace.requiredPages();
 }
Exemplo n.º 4
0
/**
 * This abstract class implements the global core functionality for all memory management schemes.
 * All global MMTk plans should inherit from this class.
 *
 * <p>All plans make a clear distinction between <i>global</i> and <i>thread-local</i> activities,
 * and divides global and local state into separate class hierarchies. Global activities must be
 * synchronized, whereas no synchronization is required for thread-local activities. There is a
 * single instance of Plan (or the appropriate sub-class), and a 1:1 mapping of PlanLocal to "kernel
 * threads" (aka CPUs). Thus instance methods of PlanLocal allow fast, unsynchronized access to
 * functions such as allocation and collection.
 *
 * <p>The global instance defines and manages static resources (such as memory and virtual memory
 * resources). This mapping of threads to instances is crucial to understanding the correctness and
 * performance properties of MMTk plans.
 */
@Uninterruptible
public abstract class Plan implements Constants {
  /** ************************************************************************** Constants */

  /* GC State */
  public static final int NOT_IN_GC = 0; // this must be zero for C code

  public static final int GC_PREPARE = 1; // before setup and obtaining root
  public static final int GC_PROPER = 2;

  /* Polling */
  public static final int DEFAULT_POLL_FREQUENCY = (128 << 10) >> LOG_BYTES_IN_PAGE;
  public static final int META_DATA_POLL_FREQUENCY = DEFAULT_POLL_FREQUENCY;

  /* Space Size Constants. */
  public static final boolean USE_CODE_SPACE = true;
  public static final float PLOS_FRAC = 0.07f;
  public static final int HEAP_FULL_MINIMUM = (1 << 17) >> LOG_BYTES_IN_PAGE; // 128K
  public static final int HEAP_FULL_PERCENTAGE = 2;

  /* Allocator Constants */
  public static final int ALLOC_DEFAULT = 0;
  public static final int ALLOC_NON_REFERENCE = 1;
  public static final int ALLOC_NON_MOVING = 2;
  public static final int ALLOC_IMMORTAL = 3;
  public static final int ALLOC_LOS = 4;
  public static final int ALLOC_PRIMITIVE_LOS = 5;
  public static final int ALLOC_GCSPY = 6;
  public static final int ALLOC_CODE = 7;
  public static final int ALLOC_LARGE_CODE = 8;
  public static final int ALLOC_HOT_CODE = USE_CODE_SPACE ? ALLOC_CODE : ALLOC_DEFAULT;
  public static final int ALLOC_COLD_CODE = USE_CODE_SPACE ? ALLOC_CODE : ALLOC_DEFAULT;
  public static final int ALLOC_STACK = ALLOC_LOS;
  public static final int ALLOCATORS = 9;
  public static final int DEFAULT_SITE = -1;

  /* Miscellaneous Constants */
  //  public static final int LOS_SIZE_THRESHOLD = SegregatedFreeListSpace.MAX_CELL_SIZE;
  public static final int NON_PARTICIPANT = 0;
  public static final boolean GATHER_WRITE_BARRIER_STATS = false;
  public static final int DEFAULT_MIN_NURSERY = (256 * 1024) >> LOG_BYTES_IN_PAGE;
  public static final int DEFAULT_MAX_NURSERY = (32 << 20) >> LOG_BYTES_IN_PAGE;
  public static final boolean SCAN_BOOT_IMAGE = true; // scan it for roots rather than trace it
  public static final int MAX_COLLECTION_ATTEMPTS = 10;
  // public static final boolean REQUIRES_LOS = VM.activePlan.constraints().requiresLOS();
  public static final int MAX_NON_LOS_DEFAULT_ALLOC_BYTES =
      VM.activePlan.constraints().maxNonLOSDefaultAllocBytes();
  public static final int MAX_NON_LOS_NONMOVING_ALLOC_BYTES =
      VM.activePlan.constraints().maxNonLOSNonMovingAllocBytes();
  public static final int MAX_NON_LOS_COPY_BYTES = VM.activePlan.constraints().maxNonLOSCopyBytes();

  /* Do we support a log bit in the object header?  Some write barriers may use it */
  public static final boolean NEEDS_LOG_BIT_IN_HEADER =
      VM.activePlan.constraints().needsLogBitInHeader();

  /** ************************************************************************** Class variables */

  /** The space that holds any VM specific objects (e.g. a boot image) */
  public static final Space vmSpace = VM.memory.getVMSpace();

  /** Any immortal objects allocated after booting are allocated here. */
  public static final ImmortalSpace immortalSpace =
      new ImmortalSpace("immortal", DEFAULT_POLL_FREQUENCY, VMRequest.create());

  /** All meta data that is used by MMTk is allocated (and accounted for) in the meta data space. */
  public static final RawPageSpace metaDataSpace =
      new RawPageSpace("meta", DEFAULT_POLL_FREQUENCY, VMRequest.create());

  /** Large objects are allocated into a special large object space. */
  public static final LargeObjectSpace loSpace =
      new LargeObjectSpace("los", DEFAULT_POLL_FREQUENCY, VMRequest.create());

  /** Space used by the sanity checker (used at runtime only if sanity checking enabled */
  public static final RawPageSpace sanitySpace =
      new RawPageSpace("sanity", Integer.MAX_VALUE, VMRequest.create());

  /**
   * Space used to allocate objects that cannot be moved. we do not need a large space as the LOS is
   * non-moving.
   */
  public static final MarkSweepSpace nonMovingSpace =
      new MarkSweepSpace("non-moving", DEFAULT_POLL_FREQUENCY, VMRequest.create());

  public static final MarkSweepSpace smallCodeSpace =
      USE_CODE_SPACE
          ? new MarkSweepSpace("sm-code", DEFAULT_POLL_FREQUENCY, VMRequest.create())
          : null;
  public static final LargeObjectSpace largeCodeSpace =
      USE_CODE_SPACE
          ? new LargeObjectSpace("lg-code", DEFAULT_POLL_FREQUENCY, VMRequest.create())
          : null;

  /* Space descriptors */
  public static final int IMMORTAL = immortalSpace.getDescriptor();
  public static final int VM_SPACE = vmSpace.getDescriptor();
  public static final int META = metaDataSpace.getDescriptor();
  public static final int LOS = loSpace.getDescriptor();
  public static final int SANITY = sanitySpace.getDescriptor();
  public static final int NON_MOVING = nonMovingSpace.getDescriptor();
  public static final int SMALL_CODE = USE_CODE_SPACE ? smallCodeSpace.getDescriptor() : 0;
  public static final int LARGE_CODE = USE_CODE_SPACE ? largeCodeSpace.getDescriptor() : 0;

  /** Timer that counts total time */
  public static final Timer totalTime = new Timer("time");

  /** Support for time-limited GCs */
  protected static long timeCap;

  /** Support for allocation-site identification */
  protected static int allocationSiteCount = 0;

  /** Global sanity checking state * */
  public static final SanityChecker sanityChecker = new SanityChecker();

  /** ************************************************************************** Constructor. */
  public Plan() {
    /* Create base option instances */
    Options.verbose = new Verbose();
    Options.verboseTiming = new VerboseTiming();
    Options.stressFactor = new StressFactor();
    Options.noFinalizer = new NoFinalizer();
    Options.noReferenceTypes = new NoReferenceTypes();
    Options.fullHeapSystemGC = new FullHeapSystemGC();
    Options.harnessAll = new HarnessAll();
    Options.ignoreSystemGC = new IgnoreSystemGC();
    Options.metaDataLimit = new MetaDataLimit();
    Options.nurserySize = new NurserySize();
    Options.variableSizeHeap = new VariableSizeHeap();
    Options.eagerMmapSpaces = new EagerMmapSpaces();
    Options.sanityCheck = new SanityCheck();
    Options.debugAddress = new DebugAddress();
    Options.perfEvents = new PerfEvents();
    Map.finalizeStaticSpaceMap();
    registerSpecializedMethods();
  }

  /** ************************************************************************** Boot. */

  /** The boot method is called early in the boot process before any allocation. */
  @Interruptible
  public void boot() {}

  /**
   * The postBoot method is called by the runtime immediately after command-line arguments are
   * available. Note that allocation must be supported prior to this point because the runtime
   * infrastructure may require allocation in order to parse the command line arguments. For this
   * reason all plans should operate gracefully on the default minimum heap size until the point
   * that boot is called.
   */
  @Interruptible
  public void postBoot() {
    VM.statistics.perfEventInit(Options.perfEvents.getValue());
    if (Options.verbose.getValue() > 2) Space.printVMMap();
    if (Options.verbose.getValue() > 3) VM.config.printConfig();
    if (Options.verbose.getValue() > 0) Stats.startAll();
    if (Options.eagerMmapSpaces.getValue()) Space.eagerlyMmapMMTkSpaces();
  }

  /** The fullyBooted method is called by the runtime just before normal execution commences. */
  @Interruptible
  public void fullyBooted() {
    initialized = true;
    if (Options.harnessAll.getValue()) harnessBegin();
  }

  /**
   * The VM is about to exit. Perform any clean up operations.
   *
   * @param value The exit value
   */
  @Interruptible
  public void notifyExit(int value) {
    if (Options.harnessAll.getValue()) harnessEnd();
    if (Options.verbose.getValue() == 1) {
      Log.write("[End ");
      totalTime.printTotalSecs();
      Log.writeln(" s]");
    } else if (Options.verbose.getValue() == 2) {
      Log.write("[End ");
      totalTime.printTotalMillis();
      Log.writeln(" ms]");
    }
    if (Options.verboseTiming.getValue()) printDetailedTiming(true);
  }

  /**
   * Any Plan can override this to provide additional plan specific timing information.
   *
   * @param totals Print totals
   */
  protected void printDetailedTiming(boolean totals) {}

  /**
   * Perform any required write barrier action when installing an object reference a boot time.
   *
   * @param reference the reference value that is to be stored
   * @return The raw value to be
   */
  public Word bootTimeWriteBarrier(Word reference) {
    return reference;
  }

  /** ************************************************************************** Allocation */
  public static int getAllocationSite(boolean compileTime) {
    if (compileTime) // a new allocation site is being compiled
    return allocationSiteCount++;
    else // an anonymous site
    return DEFAULT_SITE;
  }

  /** ************************************************************************** Collection. */

  /** Perform a (global) collection phase. */
  public abstract void collectionPhase(short phase);

  /**
   * Replace a phase.
   *
   * @param oldScheduledPhase The scheduled phase to insert after
   * @param scheduledPhase The scheduled phase to insert
   */
  @Interruptible
  public void replacePhase(int oldScheduledPhase, int scheduledPhase) {
    VM.assertions.fail("replacePhase not implemented for this plan");
  }

  /**
   * Insert a phase.
   *
   * @param markerScheduledPhase The scheduled phase to insert after
   * @param scheduledPhase The scheduled phase to insert
   */
  @Interruptible
  public void insertPhaseAfter(int markerScheduledPhase, int scheduledPhase) {
    short tempPhase = Phase.createComplex("auto-gen", null, markerScheduledPhase, scheduledPhase);
    replacePhase(markerScheduledPhase, Phase.scheduleComplex(tempPhase));
  }

  /**
   * @return Whether last GC was an exhaustive attempt to collect the heap. For many collectors this
   *     is the same as asking whether the last GC was a full heap collection.
   */
  public boolean lastCollectionWasExhaustive() {
    return lastCollectionFullHeap();
  }

  /** @return Whether last GC is a full GC. */
  public boolean lastCollectionFullHeap() {
    return true;
  }

  /** @return Is last GC a full collection? */
  public static boolean isEmergencyCollection() {
    return emergencyCollection;
  }

  /** @return True if we have run out of heap space. */
  public final boolean lastCollectionFailed() {
    return !(collectionTrigger == Collection.EXTERNAL_GC_TRIGGER
            || collectionTrigger == Collection.INTERNAL_PHASE_GC_TRIGGER)
        && (getPagesAvail() < getHeapFullThreshold() || getPagesAvail() < requiredAtStart);
  }

  /** Force the next collection to be full heap. */
  public void forceFullHeapCollection() {}

  /** @return Is current GC only collecting objects allocated since last GC. */
  public boolean isCurrentGCNursery() {
    return false;
  }

  private long lastStressPages = 0;

  /**
   * Return the expected reference count. For non-reference counting collectors this becomes a
   * true/false relationship.
   *
   * @param object The object to check.
   * @param sanityRootRC The number of root references to the object.
   * @return The expected (root excluded) reference count.
   */
  public int sanityExpectedRC(ObjectReference object, int sanityRootRC) {
    Space space = Space.getSpaceForObject(object);
    return space.isReachable(object) ? SanityChecker.ALIVE : SanityChecker.DEAD;
  }

  /**
   * Perform a linear scan of all spaces to check for possible leaks. This is only called after a
   * full-heap GC.
   *
   * @param scanner The scanner callback to use.
   */
  public void sanityLinearScan(LinearScan scanner) {}

  /** @return True is a stress test GC is required */
  @Inline
  public final boolean stressTestGCRequired() {
    long pages = Space.cumulativeCommittedPages();
    if (initialized && ((pages ^ lastStressPages) > Options.stressFactor.getPages())) {
      lastStressPages = pages;
      return true;
    } else return false;
  }

  /** ************************************************************************** GC State */
  protected static int requiredAtStart;

  protected static int collectionTrigger;
  protected static boolean emergencyCollection;
  protected static boolean awaitingAsyncCollection;
  protected static boolean stacksPrepared;

  private static boolean initialized = false;
  private static boolean collectionTriggered;
  @Entrypoint private static int gcStatus = NOT_IN_GC; // shared variable

  /** @return Is the memory management system initialized? */
  public static boolean isInitialized() {
    return initialized;
  }

  /** Has collection has triggered? */
  public static boolean isCollectionTriggered() {
    return collectionTriggered;
  }

  /** Set that a collection has been triggered. */
  public static void setCollectionTriggered() {
    collectionTriggered = true;
  }

  /** A collection has fully completed. Clear the triggered flag. */
  public static void collectionComplete() {
    collectionTriggered = false;
  }

  /**
   * Return true if stacks have been prepared in this collection cycle.
   *
   * @return True if stacks have been prepared in this collection cycle.
   */
  public static boolean stacksPrepared() {
    return stacksPrepared;
  }
  /**
   * Return true if a collection is in progress.
   *
   * @return True if a collection is in progress.
   */
  public static boolean gcInProgress() {
    return gcStatus != NOT_IN_GC;
  }

  /**
   * Return true if a collection is in progress and past the preparatory stage.
   *
   * @return True if a collection is in progress and past the preparatory stage.
   */
  public static boolean gcInProgressProper() {
    return gcStatus == GC_PROPER;
  }

  /**
   * Sets the GC status.
   *
   * @param s The new GC status.
   */
  public static void setGCStatus(int s) {
    if (gcStatus == NOT_IN_GC) {
      /* From NOT_IN_GC to any phase */
      stacksPrepared = false;
      if (Stats.gatheringStats()) {
        Stats.startGC();
        VM.activePlan.global().printPreStats();
      }
    }
    VM.memory.isync();
    gcStatus = s;
    VM.memory.sync();
    if (gcStatus == NOT_IN_GC) {
      /* From any phase to NOT_IN_GC */
      if (Stats.gatheringStats()) {
        Stats.endGC();
        VM.activePlan.global().printPostStats();
      }
    }
  }

  /** Print out statistics at the start of a GC */
  public void printPreStats() {
    if ((Options.verbose.getValue() == 1) || (Options.verbose.getValue() == 2)) {
      Log.write("[GC ");
      Log.write(Stats.gcCount());
      if (Options.verbose.getValue() == 1) {
        Log.write(" Start ");
        Plan.totalTime.printTotalSecs();
        Log.write(" s");
      } else {
        Log.write(" Start ");
        Plan.totalTime.printTotalMillis();
        Log.write(" ms");
      }
      Log.write("   ");
      Log.write(Conversions.pagesToKBytes(getPagesUsed()));
      Log.write("KB ");
      Log.flush();
    }
    if (Options.verbose.getValue() > 2) {
      Log.write("Collection ");
      Log.write(Stats.gcCount());
      Log.write(":        ");
      printUsedPages();
      Log.write("  Before Collection: ");
      Space.printUsageMB();
      if (Options.verbose.getValue() >= 4) {
        Log.write("                     ");
        Space.printUsagePages();
      }
      if (Options.verbose.getValue() >= 5) {
        Space.printVMMap();
      }
    }
  }

  /** Print out statistics at the end of a GC */
  public final void printPostStats() {
    if ((Options.verbose.getValue() == 1) || (Options.verbose.getValue() == 2)) {
      Log.write("-> ");
      Log.writeDec(Conversions.pagesToBytes(getPagesUsed()).toWord().rshl(10));
      Log.write("KB   ");
      if (Options.verbose.getValue() == 1) {
        totalTime.printLast();
        Log.writeln(" ms]");
      } else {
        Log.write("End ");
        totalTime.printTotal();
        Log.writeln(" ms]");
      }
    }
    if (Options.verbose.getValue() > 2) {
      Log.write("   After Collection: ");
      Space.printUsageMB();
      if (Options.verbose.getValue() >= 4) {
        Log.write("                     ");
        Space.printUsagePages();
      }
      if (Options.verbose.getValue() >= 5) {
        Space.printVMMap();
      }
      Log.write("                     ");
      printUsedPages();
      Log.write("    Collection time: ");
      totalTime.printLast();
      Log.writeln(" ms");
    }
  }

  public final void printUsedPages() {
    Log.write("reserved = ");
    Log.write(Conversions.pagesToMBytes(getPagesReserved()));
    Log.write(" MB (");
    Log.write(getPagesReserved());
    Log.write(" pgs)");
    Log.write("      total = ");
    Log.write(Conversions.pagesToMBytes(getTotalPages()));
    Log.write(" MB (");
    Log.write(getTotalPages());
    Log.write(" pgs)");
    Log.writeln();
  }

  /** Set the collection trigger. */
  public static void setCollectionTrigger(int trigger) {
    collectionTrigger = trigger;
  }

  /** ************************************************************************** Harness */
  protected static boolean insideHarness = false;

  /**
   * Generic hook to allow benchmarks to be harnessed. A plan may use this to perform certain
   * actions prior to the commencement of a benchmark, such as a full heap collection, turning on
   * instrumentation, etc. By default we do a full heap GC, and then start stats collection.
   */
  @Interruptible
  public static void harnessBegin() {
    // Save old values.
    boolean oldFullHeap = Options.fullHeapSystemGC.getValue();
    boolean oldIgnore = Options.ignoreSystemGC.getValue();

    // Set desired values.
    Options.fullHeapSystemGC.setValue(true);
    Options.ignoreSystemGC.setValue(false);

    // Trigger a full heap GC.
    System.gc();

    // Restore old values.
    Options.ignoreSystemGC.setValue(oldIgnore);
    Options.fullHeapSystemGC.setValue(oldFullHeap);

    // Start statistics
    insideHarness = true;
    Stats.startAll();

    VM.activePlan.global().planHarnessBegin();
  }

  public void planHarnessBegin() {
    // default empty
  }

  /**
   * Generic hook to allow benchmarks to be harnessed. A plan may use this to perform certain
   * actions after the completion of a benchmark, such as a full heap collection, turning off
   * instrumentation, etc. By default we stop all statistics objects and print their values.
   */
  @Interruptible
  public static void harnessEnd() {
    Stats.stopAll();
    insideHarness = false;
  }

  /** ************************************************************************** VM Accounting */

  /* Global accounting and static access */

  /**
   * Return the amount of <i>free memory</i>, in bytes (where free is defined as not in use). Note
   * that this may overstate the amount of <i>available memory</i>, which must account for unused
   * memory that is held in reserve for copying, and therefore unavailable for allocation.
   *
   * @return The amount of <i>free memory</i>, in bytes (where free is defined as not in use).
   */
  public static Extent freeMemory() {
    return totalMemory().minus(usedMemory());
  }

  /**
   * Return the amount of <i>available memory</i>, in bytes. Note that this accounts for unused
   * memory that is held in reserve for copying, and therefore unavailable for allocation.
   *
   * @return The amount of <i>available memory</i>, in bytes.
   */
  public static Extent availableMemory() {
    return totalMemory().minus(reservedMemory());
  }

  /**
   * Return the amount of <i>memory in use</i>, in bytes. Note that this excludes unused memory that
   * is held in reserve for copying, and therefore unavailable for allocation.
   *
   * @return The amount of <i>memory in use</i>, in bytes.
   */
  public static Extent usedMemory() {
    return Conversions.pagesToBytes(VM.activePlan.global().getPagesUsed());
  }

  /**
   * Return the amount of <i>memory in use</i>, in bytes. Note that this includes unused memory that
   * is held in reserve for copying, and therefore unavailable for allocation.
   *
   * @return The amount of <i>memory in use</i>, in bytes.
   */
  public static Extent reservedMemory() {
    return Conversions.pagesToBytes(VM.activePlan.global().getPagesReserved());
  }

  /**
   * Return the total amount of memory managed to the memory management system, in bytes.
   *
   * @return The total amount of memory managed to the memory management system, in bytes.
   */
  public static Extent totalMemory() {
    return HeapGrowthManager.getCurrentHeapSize();
  }

  /* Instance methods */

  /**
   * Return the total amount of memory managed to the memory management system, in pages.
   *
   * @return The total amount of memory managed to the memory management system, in pages.
   */
  public final int getTotalPages() {
    return totalMemory().toWord().rshl(LOG_BYTES_IN_PAGE).toInt();
  }

  /**
   * Return the number of pages available for allocation.
   *
   * @return The number of pages available for allocation.
   */
  public int getPagesAvail() {
    return getTotalPages() - getPagesReserved();
  }

  /**
   * Return the number of pages reserved for use given the pending allocation. Sub-classes must
   * override the getCopyReserve method, as the arithmetic here is fixed.
   *
   * @return The number of pages reserved given the pending allocation, including space reserved for
   *     copying.
   */
  public final int getPagesReserved() {
    return getPagesUsed() + getCollectionReserve();
  }

  /**
   * Return the number of pages reserved for collection. In most cases this is a copy reserve, all
   * subclasses that manage a copying space must add the copying contribution.
   *
   * @return The number of pages reserved given the pending allocation, including space reserved for
   *     collection.
   */
  public int getCollectionReserve() {
    return 0;
  }

  /**
   * Return the number of pages reserved for use given the pending allocation.
   *
   * @return The number of pages reserved given the pending allocation, excluding space reserved for
   *     copying.
   */
  public int getPagesUsed() {
    return loSpace.reservedPages()
        + immortalSpace.reservedPages()
        + metaDataSpace.reservedPages()
        + nonMovingSpace.reservedPages();
  }

  /**
   * Calculate the number of pages a collection is required to free to satisfy outstanding
   * allocation requests.
   *
   * @return the number of pages a collection is required to free to satisfy outstanding allocation
   *     requests.
   */
  public int getPagesRequired() {
    return loSpace.requiredPages()
        + metaDataSpace.requiredPages()
        + immortalSpace.requiredPages()
        + nonMovingSpace.requiredPages();
  }

  /**
   * The minimum number of pages a GC must have available after a collection for us to consider the
   * collection successful.
   */
  public int getHeapFullThreshold() {
    int threshold = (getTotalPages() * HEAP_FULL_PERCENTAGE) / 100;
    if (threshold < HEAP_FULL_MINIMUM) threshold = HEAP_FULL_MINIMUM;
    return threshold;
  }

  /**
   * Return the number of metadata pages reserved for use given the pending allocation.
   *
   * @return The number of pages reserved given the pending allocation, excluding space reserved for
   *     copying.
   */
  public int getMetaDataPagesUsed() {
    return metaDataSpace.reservedPages();
  }

  /**
   * Return the cycle time at which this GC should complete.
   *
   * @return The time cap for this GC (i.e. the time by which it should complete).
   */
  public static long getTimeCap() {
    return timeCap;
  }

  /**
   * ************************************************************************** Internal read/write
   * barriers.
   */

  /**
   * Store an object reference
   *
   * @param slot The location of the reference
   * @param value The value to store
   */
  @Inline
  public void storeObjectReference(Address slot, ObjectReference value) {
    slot.store(value);
  }

  /**
   * Load an object reference
   *
   * @param slot The location of the reference
   * @return the object reference loaded from slot
   */
  @Inline
  public ObjectReference loadObjectReference(Address slot) {
    return slot.loadObjectReference();
  }

  /** ************************************************************************** Collection. */

  /**
   * This method is called periodically by the allocation subsystem (by default, each time a page is
   * consumed), and provides the collector with an opportunity to collect.
   *
   * @param spaceFull Space request failed, must recover pages within 'space'.
   * @param space The space that triggered the poll.
   * @return true if a collection is required.
   */
  @LogicallyUninterruptible
  public final boolean poll(boolean spaceFull, Space space) {
    if (isCollectionTriggered()) {
      if (space == metaDataSpace) {
        /* This is not, in general, in a GC safe point. */
        return false;
      }
      /* Someone else initiated a collection, we should join it */
      logPoll(space, "Joining collection");
      VM.collection.joinCollection();
      return true;
    }

    if (collectionRequired(spaceFull)) {
      if (space == metaDataSpace) {
        /* In general we must not trigger a GC on metadata allocation since
         * this is not, in general, in a GC safe point.  Instead we initiate
         * an asynchronous GC, which will occur at the next safe point.
         */
        logPoll(space, "Asynchronous collection requested");
        setAwaitingAsyncCollection();
        return false;
      }
      logPoll(space, "Triggering collection");
      VM.collection.triggerCollection(Collection.RESOURCE_GC_TRIGGER);
      return true;
    }

    if (concurrentCollectionRequired()) {
      logPoll(space, "Triggering collection");
      VM.collection.triggerCollection(Collection.INTERNAL_PHASE_GC_TRIGGER);
      return true;
    }

    return false;
  }

  /**
   * Check whether an asynchronous collection is pending.
   *
   * <p>This is decoupled from the poll() mechanism because the triggering of asynchronous
   * collections can trigger write barriers, which can trigger an asynchronous collection. Thus, if
   * the triggering were tightly coupled with the request to alloc() within the write buffer code,
   * then inifinite regress could result. There is no race condition in the following code since
   * there is no harm in triggering the collection more than once, thus it is unsynchronized.
   */
  @Inline
  public static void checkForAsyncCollection() {
    if (awaitingAsyncCollection && VM.collection.noThreadsInGC()) {
      awaitingAsyncCollection = false;
      VM.collection.triggerAsyncCollection(Collection.RESOURCE_GC_TRIGGER);
    }
  }

  /** Request an async GC */
  protected static void setAwaitingAsyncCollection() {
    awaitingAsyncCollection = true;
  }

  /**
   * Log a message from within 'poll'
   *
   * @param space
   * @param message
   */
  private void logPoll(Space space, String message) {
    if (Options.verbose.getValue() >= 3) {
      Log.write("  [POLL] ");
      Log.write(space.getName());
      Log.write(": ");
      Log.writeln(message);
    }
  }

  /**
   * This method controls the triggering of a GC. It is called periodically during allocation.
   * Returns true to trigger a collection.
   *
   * @param spaceFull Space request failed, must recover pages within 'space'.
   * @return True if a collection is requested by the plan.
   */
  protected boolean collectionRequired(boolean spaceFull) {
    boolean stressForceGC = stressTestGCRequired();
    boolean heapFull = getPagesReserved() > getTotalPages();

    return spaceFull || stressForceGC || heapFull;
  }

  /**
   * This method controls the triggering of an atomic phase of a concurrent collection. It is called
   * periodically during allocation.
   *
   * @return True if a collection is requested by the plan.
   */
  protected boolean concurrentCollectionRequired() {
    return false;
  }

  /**
   * Start GCspy server.
   *
   * @param port The port to listen on,
   * @param wait Should we wait for a client to connect?
   */
  @Interruptible
  public void startGCspyServer(int port, boolean wait) {
    VM.assertions.fail("startGCspyServer called on non GCspy plan");
  }

  /**
   * Can this object ever move. Used by the VM to make decisions about whether it needs to copy IO
   * buffers etc.
   *
   * @param object The object in question
   * @return True if it is not possible that the object will ever move.
   */
  public boolean willNeverMove(ObjectReference object) {
    if (!VM.activePlan.constraints().movesObjects()) return true;
    if (Space.isInSpace(LOS, object)) return true;
    if (Space.isInSpace(IMMORTAL, object)) return true;
    if (Space.isInSpace(VM_SPACE, object)) return true;
    if (Space.isInSpace(NON_MOVING, object)) return true;
    if (USE_CODE_SPACE && Space.isInSpace(SMALL_CODE, object)) return true;
    if (USE_CODE_SPACE && Space.isInSpace(LARGE_CODE, object)) return true;
    /*
     * Default to false- this preserves correctness over efficiency.
     * Individual plans should override for non-moving spaces they define.
     */
    return false;
  }

  /**
   * ************************************************************************** Specialized Methods
   */

  /** Register specialized methods. */
  @Interruptible
  protected void registerSpecializedMethods() {}

  /** Get the specialized scan with the given id. */
  public final Class<?> getSpecializedScanClass(int id) {
    return TransitiveClosure.getSpecializedScanClass(id);
  }
}