/* * 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.harness; import java.util.ArrayList; import org.mmtk.harness.lang.Trace; import org.mmtk.harness.lang.Trace.Item; import org.mmtk.harness.sanity.GcSanity; import org.mmtk.harness.sanity.Sanity; import org.mmtk.harness.scheduler.Scheduler; import org.mmtk.harness.vm.ActivePlan; import org.mmtk.plan.CollectorContext; import org.mmtk.plan.Plan; import org.mmtk.utility.heap.HeapGrowthManager; import org.mmtk.utility.options.Options; import org.mmtk.vm.Collection; /** * This class represents a collector thread. */ public final class Collector implements Runnable { /** Registered collectors */ private static ArrayList<Collector> collectors = new ArrayList<Collector>(); /** * Get a collector by id. * @param id The ID of the collector * @return The collector */ public static Collector get(int id) { return collectors.get(id); } /** * @return the currently executing collector. */ public static Collector current() { Collector c = Scheduler.currentCollector(); assert c != null: "Collector.current() called from a thread without a collector context"; return c; } /** * @return The number of collector threads that have been created. */ public static int count() { return collectors.size(); } /** * Register a collector thread * @return the allocated id. */ public static synchronized int allocateCollectorId() { int id = collectors.size(); collectors.add(null); return id; } /** * Initialise numCollector collector threads. * @param numCollectors # collectors */ public static void init(int numCollectors) { for(int i = 0; i < numCollectors; i++) { Scheduler.scheduleCollector(); } } /** * The MMTk CollectorContext for this collector thread. */ private final CollectorContext context; /** * Create a new Collector */ public Collector() { try { Class<?> collectorClass = Class.forName(Harness.plan.getValue() + "Collector"); this.context = (CollectorContext)collectorClass.newInstance(); this.context.initCollector(allocateCollectorId()); } catch (Exception ex) { throw new RuntimeException("Could not create Collector", ex); } collectors.set(context.getId(), this); Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { System.err.print("Collector " + context.getId() + " caused unexpected exception: "); e.printStackTrace(); Main.exitWithFailure(); } }); } /** The number of collections that have occurred */ private static int collectionCount; /** * @return The number of GCs commenced so far */ public static int getCollectionCount() { return collectionCount; } /** The current base count of collection attempts */ private static int collectionAttemptBase; /** * @return The current base count of collection attempts */ public static int getCollectionAttemptBase() { return collectionAttemptBase; } /** Has a heap dump been requested? */ private static boolean heapDumpRequested; /** * Request a heap dump at the next GC. */ public static void requestHeapDump() { heapDumpRequested = true; } /** * Trigger a collection for the given reason * @param why Reason (as defined by MMTk VM interface) */ public static void triggerGC(int why) { Scheduler.triggerGC(why); } /** * @return the MMTk CollectorContext for this collector. */ public CollectorContext getContext() { return context; } /** * Rendezvous with all other processors, returning the rank * (that is, the order this processor arrived at the barrier). * @param where An identifier for the rendezvous point * @return The order of arrival */ public static int rendezvous(int where) { return Scheduler.rendezvous(where); } /** * The main collector execution loop. Wait for a GC to be triggered, * do the GC work and then wait again. */ public void run() { while(true) { Scheduler.waitForGCStart(); /* * Make all GC errors fatal */ try { collect(); } catch (Exception e) { e.printStackTrace(); Main.exitWithFailure(); } Scheduler.exitGC(); } } /** * Perform a GC */ private void collect() { boolean primary = context.getId() == 0; Trace.trace(Item.COLLECT, "Collection beginning, collector #%d", context.getId()); GcSanity sanity = null; TimeoutThread timeout = null; rendezvous(5000); if (primary) { Plan.setCollectionTrigger(Scheduler.getTriggerReason()); sanity = new GcSanity(); sanity.snapshotBefore(); timeout = new TimeoutThread(Harness.timeout.getValue()); } rendezvous(5001); long startTime = System.nanoTime(); boolean internalPhaseTriggered = (Scheduler.getTriggerReason() == Collection.INTERNAL_PHASE_GC_TRIGGER); boolean userTriggered = (Scheduler.getTriggerReason() == Collection.EXTERNAL_GC_TRIGGER); do { context.collect(); rendezvous(5200); if (primary) { long elapsedTime = System.nanoTime() - startTime; HeapGrowthManager.recordGCTime(elapsedTime / 1e6); if (ActivePlan.plan.lastCollectionFullHeap() && !internalPhaseTriggered) { if (Options.variableSizeHeap.getValue() && !userTriggered) { // Don't consider changing the heap size if gc was forced by System.gc() HeapGrowthManager.considerHeapSize(); } HeapGrowthManager.reset(); } if (internalPhaseTriggered) { if (ActivePlan.plan.lastCollectionFailed()) { internalPhaseTriggered = false; Plan.setCollectionTrigger(Collection.INTERNAL_GC_TRIGGER); } } collectionAttemptBase++; collectionCount += 1; } startTime = System.nanoTime(); rendezvous(5201); } while (ActivePlan.plan.lastCollectionFailed() && !Plan.isEmergencyCollection()); if (primary && !internalPhaseTriggered) { /* If the collection failed, we may need to throw OutOfMemory errors. * As we have not cleared the GC flag, allocation is not budgeted. * * This is not flawless in the case we physically can not allocate * anything right after a GC, but that case is unlikely (we can * not make it happen) and is a lot of work to get around. */ if (Plan.isEmergencyCollection()) { boolean gcFailed = ActivePlan.plan.lastCollectionFailed(); // Allocate OOMEs (some of which *may* not get used) for (Mutator mutator : Mutators.getAll()) { if (mutator.getCollectionAttempts() > 0) { /* this thread was allocating */ if (gcFailed || mutator.isPhysicalAllocationFailure()) { mutator.setOutOfMemory(true); } } } } } rendezvous(5202); if (primary) { sanity.snapshotAfter(); sanity.assertSanity(); collectionAttemptBase = 0; /* This is where we would schedule Finalization, if we supported it. */ if (heapDumpRequested) { Mutator.dumpHeap(); heapDumpRequested = false; } Plan.collectionComplete(); timeout.cancel(); if (ActivePlan.plan.lastCollectionFullHeap() && !internalPhaseTriggered) { Sanity.getObjectTable().trimToLiveSet(sanity.liveSet()); } Sanity.getObjectTable().postGcCleanup(); } rendezvous(5203); } }