Fix GC/CC scheduling (bug 630932, patch by gal/smaug, r=mrbkap).
authorAndreas Gal <gal@mozilla.com>
Wed, 16 Feb 2011 15:47:12 -0800
changeset 62789 123057f166777bd10e24f5dc7108c778141c6fc5
parent 62788 d7a8d64336ba015136f27e44f3dbaeaa070f755b
child 62790 90be6dccf2c6d5c4fefedcc2963d7e9eefd15594
push id1
push userroot
push dateTue, 10 Dec 2013 15:46:25 +0000
reviewersmrbkap
bugs630932
milestone2.0b12pre
Fix GC/CC scheduling (bug 630932, patch by gal/smaug, r=mrbkap).
dom/base/nsDOMWindowUtils.cpp
dom/base/nsGlobalWindow.cpp
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
js/src/xpconnect/src/nsXPConnect.cpp
layout/base/nsDocumentViewer.cpp
modules/libpref/src/init/all.js
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -650,17 +650,18 @@ nsDOMWindowUtils::GarbageCollect(nsICycl
 {
   // Always permit this in debug builds.
 #ifndef DEBUG
   if (!IsUniversalXPConnectCapable()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 #endif
 
-  nsJSContext::CC(aListener, PR_TRUE);
+  nsJSContext::GarbageCollectNow();
+  nsJSContext::CycleCollectNow(aListener);
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ProcessUpdates()
 {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -61,16 +61,17 @@
 // Helper Classes
 #include "nsXPIDLString.h"
 #include "nsJSUtils.h"
 #include "prmem.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "jsdbgapi.h"           // for JS_ClearWatchPointsForObject
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
+#include "nsJSEnvironment.h"
 
 // Other Classes
 #include "nsIEventListenerManager.h"
 #include "nsEscape.h"
 #include "nsStyleCoord.h"
 #include "nsMimeTypeArray.h"
 #include "nsNetUtil.h"
 #include "nsICachingChannel.h"
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -126,83 +126,43 @@ static PRLogModuleInfo* gJSDiagnostics;
 #ifndef WINCE
 #ifdef CompareString
 #undef CompareString
 #endif
 #endif // WINCE
 
 // The amount of time we wait between a request to GC (due to leaving
 // a page) and doing the actual GC.
-#define NS_GC_DELAY                 2000 // ms
-
-// The amount of time we wait until we force a GC in case the previous
-// GC timer happened to fire while we were in the middle of loading a
-// page (we'll GC once the page is loaded if that happens before this
-// amount of time has passed).
-#define NS_LOAD_IN_PROCESS_GC_DELAY 4000 // ms
+#define NS_GC_DELAY                 4000 // ms
 
 // The amount of time we wait from the first request to GC to actually
 // doing the first GC.
 #define NS_FIRST_GC_DELAY           10000 // ms
 
+// The amount of time we wait between a request to CC (after GC ran)
+// and doing the actual CC.
+#define NS_CC_DELAY                 5000 // ms
+
 #define JAVASCRIPT nsIProgrammingLanguage::JAVASCRIPT
 
-// The max number of delayed cycle collects..
-#define NS_MAX_DELAYED_CCOLLECT     45
-// The max number of user interaction notifications in inactive state before
-// we try to call cycle collector more aggressively.
-#define NS_CC_SOFT_LIMIT_INACTIVE   6
-// The max number of user interaction notifications in active state before
-// we try to call cycle collector more aggressively.
-#define NS_CC_SOFT_LIMIT_ACTIVE     12
-// When higher probability MaybeCC is used, the number of sDelayedCCollectCount
-// is multiplied with this number.
-#define NS_PROBABILITY_MULTIPLIER   3
-// Cycle collector is never called more often than every NS_MIN_CC_INTERVAL
-// milliseconds. Exceptions are low memory situation and memory pressure
-// notification.
-#define NS_MIN_CC_INTERVAL          10000 // ms
-// If previous cycle collection collected more than this number of objects,
-// the next collection will happen somewhat soon.
-// Also, if there are more than this number suspected objects, GC will be called
-// right before CC, if it wasn't called after last CC.
-#define NS_COLLECTED_OBJECTS_LIMIT  5000
-// CC will be called if GC has been called at least this number of times and
-// there are at least NS_MIN_SUSPECT_CHANGES new suspected objects.
-#define NS_MAX_GC_COUNT             5
-#define NS_MIN_SUSPECT_CHANGES      100
-// CC will be called if there are at least NS_MAX_SUSPECT_CHANGES new suspected
-// objects.
-#define NS_MAX_SUSPECT_CHANGES      1000
-
 // if you add statics here, add them to the list in nsJSRuntime::Startup
 
-static PRUint32 sDelayedCCollectCount;
-static PRUint32 sCCollectCount;
-static PRBool sUserIsActive;
-static PRTime sPreviousCCTime;
-static PRUint32 sCollectedObjectsCounts;
-static PRUint32 sSavedGCCount;
-static PRUint32 sCCSuspectChanges;
-static PRUint32 sCCSuspectedCount;
 static nsITimer *sGCTimer;
-static PRBool sReadyForGC;
+static nsITimer *sCCTimer;
 
 // The number of currently pending document loads. This count isn't
 // guaranteed to always reflect reality and can't easily as we don't
 // have an easy place to know when a load ends or is interrupted in
 // all cases. This counter also gets reset if we end up GC'ing while
 // we're waiting for a slow page to load. IOW, this count may be 0
 // even when there are pending loads.
 static PRUint32 sPendingLoadCount;
-
-// Boolean that tells us whether or not the current GC timer
-// (sGCTimer) was scheduled due to a GC timer firing while we were in
-// the middle of loading a page.
-static PRBool sLoadInProgressGCTimer;
+static PRBool sLoadingInProgress;
+
+static PRBool sPostGCEventsToConsole;
 
 nsScriptNameSpaceManager *gNameSpaceManager;
 
 static nsIJSRuntimeService *sRuntimeService;
 JSRuntime *nsJSRuntime::sRuntime;
 
 static const char kJSRuntimeServiceContractID[] =
   "@mozilla.org/js/xpc/RuntimeService;1";
@@ -214,100 +174,34 @@ static PRBool sDidShutdown;
 
 static PRInt32 sContextCount;
 
 static PRTime sMaxScriptRunTime;
 static PRTime sMaxChromeScriptRunTime;
 
 static nsIScriptSecurityManager *sSecurityManager;
 
-// nsUserActivityObserver observes user-interaction-active and
-// user-interaction-inactive notifications. It counts the number of
-// notifications and if the number is bigger than NS_CC_SOFT_LIMIT_ACTIVE
-// (in case the current notification is user-interaction-active) or
-// NS_CC_SOFT_LIMIT_INACTIVE (current notification is user-interaction-inactive)
-// MaybeCC is called with aHigherParameter set to PR_TRUE, otherwise PR_FALSE.
-//
-// When moving from active state to inactive, nsJSContext::IntervalCC() is
-// called unless the timer related to page load is active.
-
-class nsUserActivityObserver : public nsIObserver
-{
-public:
-  nsUserActivityObserver()
-  : mUserActivityCounter(0), mOldCCollectCount(0) {}
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-private:
-  PRUint32 mUserActivityCounter;
-  PRUint32 mOldCCollectCount;
-};
-
-NS_IMPL_ISUPPORTS1(nsUserActivityObserver, nsIObserver)
-
-NS_IMETHODIMP
-nsUserActivityObserver::Observe(nsISupports* aSubject, const char* aTopic,
-                                const PRUnichar* aData)
-{
-  if (mOldCCollectCount != sCCollectCount) {
-    mOldCCollectCount = sCCollectCount;
-    // Cycle collector was called between user interaction notifications, so
-    // we can reset the counter.
-    mUserActivityCounter = 0;
-  }
-  PRBool higherProbability = PR_FALSE;
-  ++mUserActivityCounter;
-  if (!strcmp(aTopic, "user-interaction-inactive")) {
-#ifdef DEBUG_smaug
-    printf("user-interaction-inactive\n");
-#endif
-    if (sUserIsActive) {
-      sUserIsActive = PR_FALSE;
-      if (!sGCTimer) {
-        nsJSContext::MaybeCC(PR_FALSE, PR_TRUE);
-        return NS_OK;
-      }
-    }
-    higherProbability = (mUserActivityCounter > NS_CC_SOFT_LIMIT_INACTIVE);
-  } else if (!strcmp(aTopic, "user-interaction-active")) {
-#ifdef DEBUG_smaug
-    printf("user-interaction-active\n");
-#endif
-    sUserIsActive = PR_TRUE;
-    higherProbability = (mUserActivityCounter > NS_CC_SOFT_LIMIT_ACTIVE);
-  } else if (!strcmp(aTopic, "xpcom-shutdown")) {
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-      obs->RemoveObserver(this, "user-interaction-active");
-      obs->RemoveObserver(this, "user-interaction-inactive");
-      obs->RemoveObserver(this, "xpcom-shutdown");
-    }
-    return NS_OK;
-  }
-  nsJSContext::MaybeCC(higherProbability);
-  return NS_OK;
-}
-
-// nsCCMemoryPressureObserver observes the memory-pressure notifications
-// and forces a cycle collection when it happens.
-
-class nsCCMemoryPressureObserver : public nsIObserver
+// nsMemoryPressureObserver observes the memory-pressure notifications
+// and forces a garbage collection and cycle collection when it happens.
+
+class nsMemoryPressureObserver : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 };
 
-NS_IMPL_ISUPPORTS1(nsCCMemoryPressureObserver, nsIObserver)
+NS_IMPL_ISUPPORTS1(nsMemoryPressureObserver, nsIObserver)
 
 NS_IMETHODIMP
-nsCCMemoryPressureObserver::Observe(nsISupports* aSubject, const char* aTopic,
-                                    const PRUnichar* aData)
+nsMemoryPressureObserver::Observe(nsISupports* aSubject, const char* aTopic,
+                                  const PRUnichar* aData)
 {
-  nsJSContext::CC(nsnull, PR_TRUE);
+  nsJSContext::GarbageCollectNow();
+  nsJSContext::CycleCollectNow();
   return NS_OK;
 }
 
 /****************************************************************
  ************************** AutoFree ****************************
  ****************************************************************/
 
 class AutoFree {
@@ -749,18 +643,16 @@ nsJSContext::DOMOperationCallback(JSCont
 
   // XXX Save the operation callback time so we can restore it after the GC,
   // because GCing can cause JS to run on our context, causing our
   // ScriptEvaluated to be called, and clearing our operation callback time.
   // See bug 302333.
   PRTime callbackTime = ctx->mOperationCallbackTime;
   PRTime modalStateTime = ctx->mModalStateTime;
 
-  JS_MaybeGC(cx);
-
   // Now restore the callback time and count, in case they got reset.
   ctx->mOperationCallbackTime = callbackTime;
   ctx->mModalStateTime = modalStateTime;
 
   PRTime now = PR_Now();
 
   if (callbackTime == 0) {
     // Initialize mOperationCallbackTime to start timing how long the
@@ -1016,24 +908,27 @@ static const char js_zeal_option_str[]  
 #endif
 static const char js_tracejit_content_str[]   = JS_OPTIONS_DOT_STR "tracejit.content";
 static const char js_tracejit_chrome_str[]    = JS_OPTIONS_DOT_STR "tracejit.chrome";
 static const char js_methodjit_content_str[]  = JS_OPTIONS_DOT_STR "methodjit.content";
 static const char js_methodjit_chrome_str[]   = JS_OPTIONS_DOT_STR "methodjit.chrome";
 static const char js_profiling_content_str[]  = JS_OPTIONS_DOT_STR "jitprofiling.content";
 static const char js_profiling_chrome_str[]   = JS_OPTIONS_DOT_STR "jitprofiling.chrome";
 static const char js_methodjit_always_str[]   = JS_OPTIONS_DOT_STR "methodjit_always";
+static const char js_memlog_option_str[] = JS_OPTIONS_DOT_STR "mem.log";
 
 int
 nsJSContext::JSOptionChangedCallback(const char *pref, void *data)
 {
   nsJSContext *context = reinterpret_cast<nsJSContext *>(data);
   PRUint32 oldDefaultJSOptions = context->mDefaultJSOptions;
   PRUint32 newDefaultJSOptions = oldDefaultJSOptions;
 
+  sPostGCEventsToConsole = nsContentUtils::GetBoolPref(js_memlog_option_str);
+
   PRBool strict = nsContentUtils::GetBoolPref(js_strict_option_str);
   if (strict)
     newDefaultJSOptions |= JSOPTION_STRICT;
   else
     newDefaultJSOptions &= ~JSOPTION_STRICT;
 
   nsIScriptGlobalObject *global = context->GetGlobalObject();
   // XXX should we check for sysprin instead of a chrome window, to make
@@ -1144,17 +1039,16 @@ nsJSContext::nsJSContext(JSRuntime *aRun
                                          JSOptionChangedCallback,
                                          this);
 
     ::JS_SetOperationCallback(mContext, DOMOperationCallback);
 
     xpc_LocalizeContext(mContext);
   }
   mIsInitialized = PR_FALSE;
-  mNumEvaluations = 0;
   mTerminations = nsnull;
   mScriptsEnabled = PR_TRUE;
   mOperationCallbackTime = 0;
   mModalStateTime = 0;
   mModalStateDepth = 0;
   mProcessingScriptTag = PR_FALSE;
 }
 
@@ -1194,17 +1088,17 @@ nsJSContext::DestroyJSContext()
   // Clear our entry in the JSContext, bugzilla bug 66413
   ::JS_SetContextPrivate(mContext, nsnull);
 
   // Unregister our "javascript.options.*" pref-changed callback.
   nsContentUtils::UnregisterPrefCallback(js_options_dot_str,
                                          JSOptionChangedCallback,
                                          this);
 
-  PRBool do_gc = mGCOnDestruction && !sGCTimer && sReadyForGC;
+  PRBool do_gc = mGCOnDestruction && !sGCTimer;
 
   // Let xpconnect destroy the JSContext when it thinks the time is right.
   nsIXPConnect *xpc = nsContentUtils::XPConnect();
   if (xpc) {
     xpc->ReleaseJSContext(mContext, !do_gc);
   } else if (do_gc) {
     ::JS_DestroyContext(mContext);
   } else {
@@ -3255,49 +3149,33 @@ nsJSContext::IsContextInitialized()
 
 void
 nsJSContext::FinalizeContext()
 {
   ;
 }
 
 void
-nsJSContext::GC()
-{
-  FireGCTimer(PR_FALSE);
-}
-
-void
 nsJSContext::ScriptEvaluated(PRBool aTerminated)
 {
   if (aTerminated && mTerminations) {
     // Make sure to null out mTerminations before doing anything that
     // might cause new termination funcs to be added!
     nsJSContext::TerminationFuncClosure* start = mTerminations;
     mTerminations = nsnull;
 
     for (nsJSContext::TerminationFuncClosure* cur = start;
          cur;
          cur = cur->mNext) {
       (*(cur->mTerminationFunc))(cur->mTerminationFuncArg);
     }
     delete start;
   }
 
-  mNumEvaluations++;
-
-#ifdef JS_GC_ZEAL
-  if (mContext->runtime->gcZeal >= 2) {
-    JS_MaybeGC(mContext);
-  } else
-#endif
-  if (mNumEvaluations > 20) {
-    mNumEvaluations = 0;
-    JS_MaybeGC(mContext);
-  }
+  JS_MaybeGC(mContext);
 
   if (aTerminated) {
     mOperationCallbackTime = 0;
     mModalStateTime = 0;
   }
 }
 
 nsresult
@@ -3364,239 +3242,253 @@ nsJSContext::SetGCOnDestruction(PRBool a
 NS_IMETHODIMP
 nsJSContext::ScriptExecuted()
 {
   ScriptEvaluated(!::JS_IsRunning(mContext));
 
   return NS_OK;
 }
 
-static inline uint32
-GetGCRunsSinceLastCC()
-{
-    // To avoid crash if nsJSRuntime is not properly initialized.
-    // See the bug 474586
-    if (!nsJSRuntime::sRuntime)
-        return 0;
-
-    // Since JS_GetGCParameter() and sSavedGCCount are unsigned, the following
-    // gives the correct result even when the GC counter wraps around
-    // UINT32_MAX since the last call to JS_GetGCParameter(). 
-    return JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER) -
-           sSavedGCCount;
-}
-
 //static
 void
-nsJSContext::CC(nsICycleCollectorListener *aListener, PRBool aForceGC)
+nsJSContext::GarbageCollectNow()
 {
   NS_TIME_FUNCTION_MIN(1.0);
 
-  ++sCCollectCount;
-#ifdef DEBUG_smaug
-  printf("Will run cycle collector (%i), %lldms since previous.\n",
-         sCCollectCount, (PR_Now() - sPreviousCCTime) / PR_USEC_PER_MSEC);
-#endif
-  sPreviousCCTime = PR_Now();
-  sDelayedCCollectCount = 0;
-  sCCSuspectChanges = 0;
-  // nsCycleCollector_collect() no longer forces a JS garbage collection,
-  // so we have to do it ourselves here.
-  if (nsContentUtils::XPConnect() &&
-      (aForceGC ||
-       (!GetGCRunsSinceLastCC() &&
-        sCCSuspectedCount > NS_COLLECTED_OBJECTS_LIMIT))) {
-    nsContentUtils::XPConnect()->GarbageCollect();
-  }
-  sCollectedObjectsCounts = nsCycleCollector_collect(aListener);
-  sCCSuspectedCount = nsCycleCollector_suspectedCount();
-  if (nsJSRuntime::sRuntime) {
-    sSavedGCCount = JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER);
+  KillGCTimer();
+
+  // Reset sPendingLoadCount in case the timer that fired was a
+  // timer we scheduled due to a normal GC timer firing while
+  // documents were loading. If this happens we're waiting for a
+  // document that is taking a long time to load, and we effectively
+  // ignore the fact that the currently loading documents are still
+  // loading and move on as if they weren't.
+  sPendingLoadCount = 0;
+  sLoadingInProgress = PR_FALSE;
+
+  nsContentUtils::XPConnect()->GarbageCollect();
+}
+
+//Static
+void
+nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener)
+{
+  if (!NS_IsMainThread()) {
+    return;
   }
-#ifdef DEBUG_smaug
-  printf("Collected %u objects, %u suspected objects, took %lldms\n",
-         sCollectedObjectsCounts, sCCSuspectedCount,
-         (PR_Now() - sPreviousCCTime) / PR_USEC_PER_MSEC);
-#endif
-}
-
-//static
-PRBool
-nsJSContext::MaybeCC(PRBool aHigherProbability, PRBool aForceGC)
-{
-  ++sDelayedCCollectCount;
-
-  // Don't check suspected count if CC will be called anyway.
-  if (sCCSuspectChanges <= NS_MIN_SUSPECT_CHANGES ||
-      GetGCRunsSinceLastCC() <= NS_MAX_GC_COUNT) {
-#ifdef DEBUG_smaug
+
+  NS_TIME_FUNCTION_MIN(1.0);
+
+  KillCCTimer();
+
+  PRTime start = PR_Now();
+
+  PRUint32 suspected = nsCycleCollector_suspectedCount();
+  PRUint32 collected = nsCycleCollector_collect(aListener);
+
+  // If we collected cycles, poke the GC since more objects might be unreachable now.
+  if (collected > 0) {
+    PokeGC();
+  }
+
+  if (sPostGCEventsToConsole) {
     PRTime now = PR_Now();
-#endif
-    PRUint32 suspected = nsCycleCollector_suspectedCount();
-#ifdef DEBUG_smaug
-    printf("%u suspected objects (%lldms), sCCSuspectedCount %u\n",
-            suspected, (PR_Now() - now) / PR_USEC_PER_MSEC,
-            sCCSuspectedCount);
-#endif
-    // Update only when suspected count has increased.
-    if (suspected > sCCSuspectedCount) {
-      sCCSuspectChanges += (suspected - sCCSuspectedCount);
-      sCCSuspectedCount = suspected;
+    NS_NAMED_LITERAL_STRING(kFmt,
+                            "CC timestamp: %lld, collected: %lu, suspected: %lu, duration: %llu ms.");
+    nsString msg;
+    msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), now,
+                                        collected, suspected,
+                                        (now - start) / PR_USEC_PER_MSEC));
+    nsCOMPtr<nsIConsoleService> cs =
+      do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    if (cs) {
+      cs->LogStringMessage(msg.get());
     }
   }
-#ifdef DEBUG_smaug
-  printf("sCCSuspectChanges %u, GC runs %u\n",
-         sCCSuspectChanges, GetGCRunsSinceLastCC());
-#endif
-
-  // Increase the probability also if the previous call to cycle collector
-  // collected something.
-  if (aHigherProbability ||
-      sCollectedObjectsCounts > NS_COLLECTED_OBJECTS_LIMIT) {
-    sDelayedCCollectCount *= NS_PROBABILITY_MULTIPLIER;
-  } else if (!sUserIsActive && sCCSuspectChanges > NS_MAX_SUSPECT_CHANGES) {
-    // If user is inactive and there are lots of new suspected objects, 
-    // increase the probability for cycle collection.
-    sDelayedCCollectCount += (sCCSuspectChanges / NS_MAX_SUSPECT_CHANGES);
-  }
-
-  if (!sGCTimer &&
-      (sDelayedCCollectCount > NS_MAX_DELAYED_CCOLLECT) &&
-      ((sCCSuspectChanges > NS_MIN_SUSPECT_CHANGES &&
-        GetGCRunsSinceLastCC() > NS_MAX_GC_COUNT) ||
-       (sCCSuspectChanges > NS_MAX_SUSPECT_CHANGES))) {
-    return IntervalCC(aForceGC);
-  }
-  return PR_FALSE;
-}
-
-//static
-void
-nsJSContext::CCIfUserInactive()
-{
-  if (sUserIsActive) {
-    MaybeCC(PR_TRUE, PR_TRUE);
-  } else {
-    IntervalCC(PR_TRUE);
-  }
-}
-
-//static
-void
-nsJSContext::MaybeCCIfUserInactive()
-{
-  if (!sUserIsActive) {
-    MaybeCC(PR_FALSE);
-  }
-}
-
-//static
-PRBool
-nsJSContext::IntervalCC(PRBool aForceGC)
-{
-  if ((PR_Now() - sPreviousCCTime) >=
-      PRTime(NS_MIN_CC_INTERVAL * PR_USEC_PER_MSEC)) {
-    nsJSContext::CC(nsnull, aForceGC);
-    return PR_TRUE;
-  }
-#ifdef DEBUG_smaug
-  printf("Running CC was delayed because of NS_MIN_CC_INTERVAL.\n");
-#endif
-  return PR_FALSE;
 }
 
 // static
 void
 GCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   NS_RELEASE(sGCTimer);
 
-  if (sPendingLoadCount == 0 || sLoadInProgressGCTimer) {
-    sLoadInProgressGCTimer = PR_FALSE;
-
-    // Reset sPendingLoadCount in case the timer that fired was a
-    // timer we scheduled due to a normal GC timer firing while
-    // documents were loading. If this happens we're waiting for a
-    // document that is taking a long time to load, and we effectively
-    // ignore the fact that the currently loading documents are still
-    // loading and move on as if they weren't.
-    sPendingLoadCount = 0;
-
-    nsJSContext::CCIfUserInactive();
-  } else {
-    nsJSContext::FireGCTimer(PR_TRUE);
-  }
-
-  sReadyForGC = PR_TRUE;
+  nsJSContext::GarbageCollectNow();
+}
+
+// static
+void
+CCTimerFired(nsITimer *aTimer, void *aClosure)
+{
+  NS_RELEASE(sCCTimer);
+
+  nsJSContext::CycleCollectNow();
 }
 
 // static
 void
 nsJSContext::LoadStart()
 {
+  sLoadingInProgress = PR_TRUE;
   ++sPendingLoadCount;
 }
 
 // static
 void
 nsJSContext::LoadEnd()
 {
+  if (!sLoadingInProgress)
+    return;
+
   // sPendingLoadCount is not a well managed load counter (and doesn't
   // need to be), so make sure we don't make it wrap backwards here.
   if (sPendingLoadCount > 0) {
     --sPendingLoadCount;
+    return;
   }
 
-  if (!sPendingLoadCount && sLoadInProgressGCTimer) {
-    sGCTimer->Cancel();
-    NS_RELEASE(sGCTimer);
-    sLoadInProgressGCTimer = PR_FALSE;
-
-    CCIfUserInactive();
-  }
+  // Its probably a good idea to GC soon since we have finished loading.
+  sLoadingInProgress = PR_FALSE;
+  PokeGC();
 }
 
 // static
 void
-nsJSContext::FireGCTimer(PRBool aLoadInProgress)
+nsJSContext::PokeGC()
 {
   if (sGCTimer) {
     // There's already a timer for GC'ing, just return
     return;
   }
 
   CallCreateInstance("@mozilla.org/timer;1", &sGCTimer);
 
   if (!sGCTimer) {
     NS_WARNING("Failed to create timer");
 
-    // Reset sLoadInProgressGCTimer since we're not able to fire the
-    // timer.
-    sLoadInProgressGCTimer = PR_FALSE;
-
-    CCIfUserInactive();
+    GarbageCollectNow();
     return;
   }
 
   static PRBool first = PR_TRUE;
 
   sGCTimer->InitWithFuncCallback(GCTimerFired, nsnull,
-                                 first ? NS_FIRST_GC_DELAY :
-                                 aLoadInProgress ? NS_LOAD_IN_PROCESS_GC_DELAY :
-                                                   NS_GC_DELAY,
+                                 first
+                                 ? NS_FIRST_GC_DELAY
+                                 : NS_GC_DELAY,
                                  nsITimer::TYPE_ONE_SHOT);
 
-  sLoadInProgressGCTimer = aLoadInProgress;
-
   first = PR_FALSE;
 }
 
+// static
+void
+nsJSContext::MaybePokeCC()
+{
+  if (nsCycleCollector_suspectedCount() > 1000) {
+    PokeCC();
+  }
+}
+
+// static
+void
+nsJSContext::PokeCC()
+{
+  if (sCCTimer) {
+    // There's already a timer for GC'ing, just return
+    return;
+  }
+
+  CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
+
+  if (!sCCTimer) {
+    NS_WARNING("Failed to create timer");
+
+    CycleCollectNow();
+    return;
+  }
+
+  sCCTimer->InitWithFuncCallback(CCTimerFired, nsnull,
+                                 NS_CC_DELAY,
+                                 nsITimer::TYPE_ONE_SHOT);
+}
+
+//static
+void
+nsJSContext::KillGCTimer()
+{
+  if (sGCTimer) {
+    sGCTimer->Cancel();
+
+    NS_RELEASE(sGCTimer);
+  }
+}
+
+//static
+void
+nsJSContext::KillCCTimer()
+{
+  if (sCCTimer) {
+    sCCTimer->Cancel();
+
+    NS_RELEASE(sCCTimer);
+  }
+}
+
+void
+nsJSContext::GC()
+{
+  PokeGC();
+}
+
 static JSBool
 DOMGCCallback(JSContext *cx, JSGCStatus status)
 {
+  static PRTime start;
+
+  if (sPostGCEventsToConsole && NS_IsMainThread()) {
+    if (status == JSGC_BEGIN) {
+      start = PR_Now();
+    } else if (status == JSGC_END) {
+      PRTime now = PR_Now();
+      NS_NAMED_LITERAL_STRING(kFmt, "GC timestamp: %lld, duration: %llu ms.");
+      nsString msg;
+      msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), now,
+                (now - start) / PR_USEC_PER_MSEC));
+      nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+      if (cs) {
+        cs->LogStringMessage(msg.get());
+      }
+    }
+  }
+
+  if (status == JSGC_END) {
+    if (sGCTimer) {
+      // If we were waiting for a GC to happen, kill the timer.
+      nsJSContext::KillGCTimer();
+
+      // If this is a compartment GC, restart it. We still want
+      // a full GC to happen. Compartment GCs usually happen as a
+      // result of last-ditch or MaybeGC. In both cases its
+      // probably a time of heavy activity and we want to delay
+      // the full GC, but we do want it to happen eventually.
+      if (cx->runtime->gcTriggerCompartment) {
+        nsJSContext::PokeGC();
+
+        // We poked the GC, so we can kill any pending CC here.
+        nsJSContext::KillCCTimer();
+      }
+    } else {
+      // If this was a full GC, poke the CC to run soon.
+      if (!cx->runtime->gcTriggerCompartment) {
+        nsJSContext::PokeCC();
+      }
+    }
+  }
+
   JSBool result = gOldJSGCCallback ? gOldJSGCCallback(cx, status) : JS_TRUE;
 
   if (status == JSGC_BEGIN && !NS_IsMainThread())
     return JS_FALSE;
 
   return result;
 }
 
@@ -3687,28 +3579,20 @@ nsJSRuntime::ParseVersion(const nsString
     return NS_OK;
 }
 
 //static
 void
 nsJSRuntime::Startup()
 {
   // initialize all our statics, so that we can restart XPCOM
-  sDelayedCCollectCount = 0;
-  sCCollectCount = 0;
-  sUserIsActive = PR_FALSE;
-  sPreviousCCTime = PR_Now();
-  sCollectedObjectsCounts = 0;
-  sSavedGCCount = 0;
-  sCCSuspectChanges = 0;
-  sCCSuspectedCount = 0;
-  sGCTimer = nsnull;
-  sReadyForGC = PR_FALSE;
-  sLoadInProgressGCTimer = PR_FALSE;
+  sGCTimer = sCCTimer = nsnull;
   sPendingLoadCount = 0;
+  sLoadingInProgress = PR_FALSE;
+  sPostGCEventsToConsole = PR_FALSE;
   gNameSpaceManager = nsnull;
   sRuntimeService = nsnull;
   sRuntime = nsnull;
   gOldJSGCCallback = nsnull;
   sIsInitialized = PR_FALSE;
   sDidShutdown = PR_FALSE;
   sContextCount = 0;
   sSecurityManager = nsnull;
@@ -3867,18 +3751,16 @@ nsJSRuntime::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Let's make sure that our main thread is the same as the xpcom main thread.
   NS_ASSERTION(NS_IsMainThread(), "bad");
 
   NS_ASSERTION(!gOldJSGCCallback,
                "nsJSRuntime initialized more than once");
 
-  sSavedGCCount = JS_GetGCParameter(nsJSRuntime::sRuntime, JSGC_NUMBER);
-
   // Save the old GC callback to chain to it, for GC-observing generality.
   gOldJSGCCallback = ::JS_SetGCCallbackRT(sRuntime, DOMGCCallback);
 
   JSSecurityCallbacks *callbacks = JS_GetRuntimeSecurityCallbacks(sRuntime);
   NS_ASSERTION(callbacks, "SecMan should have set security callbacks!");
 
   callbacks->findObjectPrincipals = ObjectPrincipalFinder;
 
@@ -3930,25 +3812,20 @@ nsJSRuntime::Init()
                                        SetMemoryGCModePrefChangedCallback,
                                        nsnull);
   SetMemoryGCModePrefChangedCallback("javascript.options.mem.gc_per_compartment",
                                      nsnull);
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (!obs)
     return NS_ERROR_FAILURE;
-  nsIObserver* activityObserver = new nsUserActivityObserver();
-  NS_ENSURE_TRUE(activityObserver, NS_ERROR_OUT_OF_MEMORY);
-  obs->AddObserver(activityObserver, "user-interaction-inactive", PR_FALSE);
-  obs->AddObserver(activityObserver, "user-interaction-active", PR_FALSE);
-  obs->AddObserver(activityObserver, "xpcom-shutdown", PR_FALSE);
-
-  nsIObserver* ccMemPressureObserver = new nsCCMemoryPressureObserver();
-  NS_ENSURE_TRUE(ccMemPressureObserver, NS_ERROR_OUT_OF_MEMORY);
-  obs->AddObserver(ccMemPressureObserver, "memory-pressure", PR_FALSE);
+
+  nsIObserver* memPressureObserver = new nsMemoryPressureObserver();
+  NS_ENSURE_TRUE(memPressureObserver, NS_ERROR_OUT_OF_MEMORY);
+  obs->AddObserver(memPressureObserver, "memory-pressure", PR_FALSE);
 
   sIsInitialized = PR_TRUE;
 
   return NS_OK;
 }
 
 //static
 nsScriptNameSpaceManager*
@@ -3967,26 +3844,18 @@ nsJSRuntime::GetNameSpaceManager()
 
   return gNameSpaceManager;
 }
 
 /* static */
 void
 nsJSRuntime::Shutdown()
 {
-  if (sGCTimer) {
-    // We're being shut down, if we have a GC timer scheduled, cancel
-    // it. The DOM factory will do one final GC once it's shut down.
-
-    sGCTimer->Cancel();
-
-    NS_RELEASE(sGCTimer);
-
-    sLoadInProgressGCTimer = PR_FALSE;
-  }
+  nsJSContext::KillGCTimer();
+  nsJSContext::KillCCTimer();
 
   NS_IF_RELEASE(gNameSpaceManager);
 
   if (!sContextCount) {
     // We're being shutdown, and there are no more contexts
     // alive, release the JS runtime service and the security manager.
 
     if (sRuntimeService && sSecurityManager) {
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -143,18 +143,16 @@ public:
   virtual nsresult InitContext();
   virtual nsresult CreateOuterObject(nsIScriptGlobalObject *aGlobalObject,
                                      nsIScriptGlobalObject *aCurrentInner);
   virtual nsresult SetOuterObject(void *aOuterObject);
   virtual nsresult InitOuterWindow();
   virtual PRBool IsContextInitialized();
   virtual void FinalizeContext();
 
-  virtual void GC();
-
   virtual void ScriptEvaluated(PRBool aTerminated);
   virtual nsresult SetTerminationFunction(nsScriptTerminationFunc aFunc,
                                           nsISupports* aRef);
   virtual PRBool GetScriptsEnabled();
   virtual void SetScriptsEnabled(PRBool aEnabled, PRBool aFireTimeouts);
 
   virtual nsresult SetProperty(void *aTarget, const char *aPropName, nsISupports *aVal);
 
@@ -182,47 +180,27 @@ public:
   virtual void EnterModalState();
   virtual void LeaveModalState();
 
   NS_DECL_NSIXPCSCRIPTNOTIFY
 
   static void LoadStart();
   static void LoadEnd();
 
-  // CC does always call cycle collector and it also updates the counters
-  // that MaybeCC uses.
-  static void CC(nsICycleCollectorListener *aListener,
-                 PRBool aForceGC = PR_FALSE);
+  static void GarbageCollectNow();
+  static void CycleCollectNow(nsICycleCollectorListener *aListener = nsnull);
+
+  static void PokeGC();
+  static void KillGCTimer();
 
-  // MaybeCC calls cycle collector if certain conditions are fulfilled.
-  // The conditions are:
-  // - The timer related to page load (sGCTimer) must not be active.
-  // - At least NS_MIN_CC_INTERVAL milliseconds must have elapsed since the
-  //   previous cycle collector call.
-  // - Certain number of MaybeCC calls have occurred.
-  //   The number of needed MaybeCC calls depends on the aHigherProbability
-  //   parameter. If the parameter is true, probability for calling cycle
-  //   collector rises increasingly. If the parameter is all the time false,
-  //   at least NS_MAX_DELAYED_CCOLLECT MaybeCC calls are needed.
-  //   If the previous call to cycle collector did collect something,
-  //   MaybeCC works effectively as if aHigherProbability was true.
-  // @return PR_TRUE if cycle collector was called.
-  static PRBool MaybeCC(PRBool aHigherProbability, PRBool aForceGC = PR_FALSE);
+  static void PokeCC();
+  static void MaybePokeCC();
+  static void KillCCTimer();
 
-  // IntervalCC() calls CC() if at least NS_MIN_CC_INTERVAL milliseconds have
-  // elapsed since the previous cycle collector call.
-  static PRBool IntervalCC(PRBool aForceGC = PR_FALSE);
-
-  // Calls IntervalCC(PR_TRUE) if user is currently inactive,
-  // otherwise MaybeCC(PR_TRUE, PR_TRUE)
-  static void CCIfUserInactive();
-
-  static void MaybeCCIfUserInactive();
-
-  static void FireGCTimer(PRBool aLoadInProgress);
+  virtual void GC();
 
 protected:
   nsresult InitializeExternalClasses();
 
   // Helper to convert xpcom datatypes to jsvals.
   nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
                                    void *aScope,
                                    PRUint32 *aArgc,
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -2377,17 +2377,17 @@ nsXPConnect::OnProcessNextEvent(nsIThrea
 }
 
 NS_IMETHODIMP
 nsXPConnect::AfterProcessNextEvent(nsIThreadInternal *aThread,
                                    PRUint32 aRecursionDepth)
 {
     // Call cycle collector occasionally.
     if (NS_IsMainThread()) {
-        nsJSContext::MaybeCCIfUserInactive();
+        nsJSContext::MaybePokeCC();
     }
 
     return Pop(nsnull);
 }
 
 NS_IMETHODIMP
 nsXPConnect::OnDispatchedEvent(nsIThreadInternal* aThread)
 {
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1284,16 +1284,19 @@ DocumentViewerImpl::PageHide(PRBool aIsU
 
   // inform the window so that the focus state is reset.
   NS_ENSURE_STATE(mDocument);
   nsPIDOMWindow *window = mDocument->GetWindow();
   if (window)
     window->PageHidden();
 
   if (aIsUnload) {
+    // Poke the GC. The window might be collectable garbage now.
+    nsJSContext::PokeGC();
+
     // if Destroy() was called during OnPageHide(), mDocument is nsnull.
     NS_ENSURE_STATE(mDocument);
 
     // First, get the window from the document...
     nsPIDOMWindow *window = mDocument->GetWindow();
 
     if (!window) {
       // Fail if no window is available...
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -608,16 +608,17 @@ pref("javascript.options.methodjit_alway
 // This preference limits the memory usage of javascript.
 // If you want to change these values for your device,
 // please find Bug 417052 comment 17 and Bug 456721
 // Comment 32 and Bug 613551.
 pref("javascript.options.mem.high_water_mark", 128);
 pref("javascript.options.mem.max", -1);
 pref("javascript.options.mem.gc_frequency",   300);
 pref("javascript.options.mem.gc_per_compartment", true);
+pref("javascript.options.mem.log", false);
 
 // advanced prefs
 pref("advanced.mailftp",                    false);
 pref("image.animation_mode",                "normal");
 
 // Same-origin policy for file URIs, "false" is traditional
 pref("security.fileuri.strict_origin_policy", true);