diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 12ab6e9..e6ce197 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1419,7 +1419,7 @@ function drupal_serve_page_from_cache(stdClass $cache) {
  * Defines the critical hooks that force modules to always be loaded.
  */
 function bootstrap_hooks() {
-  return array('boot', 'exit', 'watchdog', 'language_init');
+  return array('boot', 'watchdog', 'language_init');
 }
 
 /**
@@ -2340,11 +2340,6 @@ function _drupal_bootstrap_page_cache() {
         bootstrap_invoke_all('boot');
       }
       drupal_serve_page_from_cache($cache);
-      // If the skipping of the bootstrap hooks is not enforced, call
-      // hook_exit.
-      if (variable_get('page_cache_invoke_hooks', TRUE)) {
-        bootstrap_invoke_all('exit');
-      }
       // We are done.
       exit;
     }
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 54eaf18..6b33704 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -172,9 +172,7 @@ function overlay_init() {
  * the supplemental regions of the overlay on the next page request.
  */
 function overlay_exit() {
-  // Check that we are in an overlay child page. Note that this should never
-  // return TRUE on a cached page view, since the child mode is not set until
-  // overlay_init() is called.
+  // Check that we are in an overlay child page.
   if (overlay_get_mode() == 'child') {
     // Load any markup that was stored earlier in the page request, via calls
     // to overlay_store_rendered_content(). If none was stored, this is not a
diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookBootExitTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookBootExitTest.php
index e9eff7a..5520551 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookBootExitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookBootExitTest.php
@@ -39,30 +39,40 @@ function testHookBootExit() {
     $config->save();
 
     $this->drupalGet('');
-    $calls = 1;
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, 'hook_boot called with disabled cache.');
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, 'hook_exit called with disabled cache.');
+    $boot_calls = $exit_calls = 1;
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $boot_calls, 'hook_boot called with disabled cache.');
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $exit_calls, 'hook_exit called with disabled cache.');
 
-    // Test with normal cache. Boot and exit should be called.
+    // Test with normal cache. On the first call, boot and exit should both
+    // fire (since the cache is empty), but on the second call, exit should not
+    // be fired.
     $config->set('cache.page.enabled', 1);
     $config->save();
     $this->drupalGet('');
-    $calls++;
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, 'hook_boot called with normal cache.');
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, 'hook_exit called with normal cache.');
+    $boot_calls++;
+    $exit_calls++;
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $boot_calls, 'hook_boot called with normal cache and no cached page.');
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $exit_calls, 'hook_exit called with normal cache and no cached page.');
+    $this->assertTrue(cache('page')->get(url('', array('absolute' => TRUE))), 'Page has been cached.');
+    $this->drupalGet('');
+    $boot_calls++;
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $boot_calls, 'hook_boot called with normal cache and a cached page');
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $exit_calls, 'hook_exit not called with normal cache and a cached page.');
 
-    // Boot and exit should not fire since the page is cached.
+    // Test with aggressive cache. Boot and exit should not fire since the page
+    // is cached.
     variable_set('page_cache_invoke_hooks', FALSE);
     $this->assertTrue(cache('page')->get(url('', array('absolute' => TRUE))), 'Page has been cached.');
     $this->drupalGet('');
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, 'hook_boot not called with aggressive cache and a cached page.');
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, 'hook_exit not called with aggressive cache and a cached page.');
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $boot_calls, 'hook_boot not called with aggressive cache and a cached page.');
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $exit_calls, 'hook_exit not called with aggressive cache and a cached page.');
 
     // Test with page cache cleared, boot and exit should be called.
     $this->assertTrue(db_delete('cache_page')->execute(), 'Page cache cleared.');
     $this->drupalGet('');
-    $calls++;
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, 'hook_boot called with aggressive cache and no cached page.');
-    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, 'hook_exit called with aggressive cache and no cached page.');
+    $boot_calls++;
+    $exit_calls++;
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $boot_calls, 'hook_boot called with aggressive cache and no cached page.');
+    $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $exit_calls, 'hook_exit called with aggressive cache and no cached page.');
   }
 }
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 1792296..442400d 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -330,26 +330,19 @@ function hook_element_info_alter(&$type) {
 /**
  * Perform cleanup tasks.
  *
- * This hook is run at the end of each page request. It is often used for
- * page logging and specialized cleanup. This hook MUST NOT print anything
- * because by the time it runs the response is already sent to the browser.
+ * This hook is run at the end of each non-cached page request. It is often
+ * used for page logging and specialized cleanup. This hook MUST NOT print
+ * anything because by the time it runs the response is already sent to the
+ * browser.
  *
- * Only use this hook if your code must run even for cached page views.
- * If you have code which must run once on all non-cached pages, use
- * hook_init() instead. That is the usual case. If you implement this hook
- * and see an error like 'Call to undefined function', it is likely that
- * you are depending on the presence of a module which has not been loaded yet.
- * It is not loaded because Drupal is still in bootstrap mode.
+ * This hook is not run on cached pages.
  *
  * @param $destination
  *   If this hook is invoked as part of a drupal_goto() call, then this argument
  *   will be a fully-qualified URL that is the destination of the redirect.
  */
 function hook_exit($destination = NULL) {
-  db_update('counter')
-    ->expression('hits', 'hits + 1')
-    ->condition('type', 1)
-    ->execute();
+  watchdog('mymodule', 'Page was built for user %name.', array('%name' => user_format_name($GLOBALS['user'])), WATCHDOG_INFO);
 }
 
 /**
