diff --git a/core/authorize.php b/core/authorize.php
index 114dcd3..c943f9f 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -32,7 +32,7 @@
  * Global flag to identify update.php and authorize.php runs.
  *
  * Identifies update.php and authorize.php runs, avoiding unwanted operations
- * such as hook_init() and hook_exit() invokes, css/js preprocessing and
+ * such as hook_init() invocations, css/js preprocessing and
  * translation, and solves some theming issues. The flag is checked in other
  * places in Drupal code (not just authorize.php).
  */
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 9958553..c8e22db 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -709,7 +709,7 @@ function drupal_goto($path = '', array $options = array(), $http_response_code =
   // The "Location" header sends a redirect status code to the HTTP daemon. In
   // some cases this can be wrong, so we make sure none of the code below the
   // drupal_goto() call gets executed upon redirection.
-  drupal_exit($url);
+  drupal_exit();
 }
 
 /**
@@ -2295,17 +2295,9 @@ function l($text, $path, array $options = array()) {
  * Performs end-of-request tasks.
  *
  * There should rarely be a reason to call exit instead of drupal_exit();
- *
- * @param $destination
- *   If this function is called from drupal_goto(), then this argument
- *   will be a fully-qualified URL that is the destination of the redirect.
- *   This should be passed along to hook_exit() implementations.
  */
-function drupal_exit($destination = NULL) {
+function drupal_exit() {
   if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
-    if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
-      module_invoke_all('exit', $destination);
-    }
     drupal_session_commit();
   }
   exit;
diff --git a/core/includes/path.inc b/core/includes/path.inc
index 6e2c19e..1a6c8cd 100644
--- a/core/includes/path.inc
+++ b/core/includes/path.inc
@@ -3,10 +3,6 @@
 /**
  * @file
  * Functions to handle paths in Drupal.
- *
- * These functions are not loaded for cached pages, but modules that need
- * to use them in hook exit() can make them available, by executing
- * "drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);".
  */
 
 /**
diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
index eb295bd..d2e1078 100644
--- a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
@@ -42,7 +42,6 @@ function __construct(ModuleHandlerInterface $module_handler) {
    *   The Event to process.
    */
   public function onTerminate(PostResponseEvent $event) {
-    $this->moduleHandler->invokeAll('exit');
     $request_method = $event->getRequest()->getMethod();
     // Check whether we need to write the module implementations cache. We do
     // not want to cache hooks which are only invoked on HTTP POST requests
diff --git a/core/modules/overlay/lib/Drupal/overlay/EventSubscriber/OverlaySubscriber.php b/core/modules/overlay/lib/Drupal/overlay/EventSubscriber/OverlaySubscriber.php
new file mode 100644
index 0000000..d5fe393
--- /dev/null
+++ b/core/modules/overlay/lib/Drupal/overlay/EventSubscriber/OverlaySubscriber.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\overlay\EventSubscriber\OverlaySubscriber.
+ */
+
+namespace Drupal\overlay\EventSubscriber;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Overlay subscriber for controller requests.
+ */
+class OverlaySubscriber implements EventSubscriberInterface {
+
+  /**
+   * Performs end of request tasks.
+   *
+   * When viewing an overlay child page, check if we need to trigger a refresh of
+   * the supplemental regions of the overlay on the next page request.
+   */
+  public function onResponse(FilterResponseEvent $event) {
+    // 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
+      // page request where we expect any changes to the overlay supplemental
+      // regions to have occurred, so we do not need to proceed any further.
+      $original_markup = overlay_get_rendered_content();
+      if (!empty($original_markup)) {
+        // Compare the original markup to the current markup that we get from
+        // rendering each overlay supplemental region now. If they don't match,
+        // something must have changed, so we request a refresh of that region
+        // within the parent window on the next page request.
+        foreach (overlay_supplemental_regions() as $region) {
+          if (!isset($original_markup[$region]) || $original_markup[$region] != overlay_render_region($region)) {
+            overlay_request_refresh($region);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements EventSubscriberInterface::getSubscribedEvents().
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::RESPONSE][] = array('onResponse');
+
+    return $events;
+  }
+}
diff --git a/core/modules/overlay/lib/Drupal/overlay/OverlayBundle.php b/core/modules/overlay/lib/Drupal/overlay/OverlayBundle.php
new file mode 100644
index 0000000..e99ed01
--- /dev/null
+++ b/core/modules/overlay/lib/Drupal/overlay/OverlayBundle.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\overlay\OverlayBundle.
+ */
+
+namespace Drupal\overlay;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Defines the Overlay bundle.
+ */
+class OverlayBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('overlay.subscriber', 'Drupal\overlay\EventSubscriber\OverlaySubscriber')
+      ->addTag('event_subscriber');
+  }
+}
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index 1893a15..f6d2190 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -165,34 +165,6 @@ function overlay_init() {
 }
 
 /**
- * Implements hook_exit().
- *
- * When viewing an overlay child page, check if we need to trigger a refresh of
- * the supplemental regions of the overlay on the next page request.
- */
-function overlay_exit() {
-  // 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
-    // page request where we expect any changes to the overlay supplemental
-    // regions to have occurred, so we do not need to proceed any further.
-    $original_markup = overlay_get_rendered_content();
-    if (!empty($original_markup)) {
-      // Compare the original markup to the current markup that we get from
-      // rendering each overlay supplemental region now. If they don't match,
-      // something must have changed, so we request a refresh of that region
-      // within the parent window on the next page request.
-      foreach (overlay_supplemental_regions() as $region) {
-        if (!isset($original_markup[$region]) || $original_markup[$region] != overlay_render_region($region)) {
-          overlay_request_refresh($region);
-        }
-      }
-    }
-  }
-}
-
-/**
  * Implements hook_library_info().
  */
 function overlay_library_info() {
@@ -717,9 +689,10 @@ function overlay_overlay_child_initialize() {
   // have an indication that something in the supplemental regions of the
   // overlay might change during the current page request. We therefore store
   // the initial rendered content of those regions here, so that we can compare
-  // it to the same content rendered in overlay_exit(), at the end of the page
-  // request. This allows us to check if anything actually did change, and, if
-  // so, trigger an immediate Ajax refresh of the parent window.
+  // it to the same content rendered in OverlaySubscriber::onResponse(),
+  // at the end of the page request. This allows us to check if anything
+  // actually did change, and, if so, trigger an immediate Ajax refresh
+  // of the parent window.
   $token = drupal_container()->get('request')->query->get('token');
   if (!empty($_POST) || isset($token)) {
     foreach (overlay_supplemental_regions() as $region) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookExitTest.php b/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookExitTest.php
deleted file mode 100644
index 0673340..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Bootstrap/HookExitTest.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\system\Tests\Bootstrap\HookExitTest.
- */
-
-namespace Drupal\system\Tests\Bootstrap;
-
-use Drupal\simpletest\WebTestBase;
-
-/**
- * Tests hook_exit().
- */
-class HookExitTest extends WebTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('system_test', 'dblog');
-
-  public static function getInfo() {
-    return array(
-      'name' => 'Exit hook invocation',
-      'description' => 'Test that hook_exit() is called correctly.',
-      'group' => 'Bootstrap',
-    );
-  }
-
-  /**
-   * Tests calling of hook_exit().
-   */
-  function testHookExit() {
-    // Test with cache disabled. Exit should always fire.
-    $config = config('system.performance');
-    $config->set('cache.page.enabled', 0);
-    $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_exit'))->fetchField(), $calls, 'hook_exit called with disabled cache.');
-
-    // Test with normal cache. On the first call, exit should fire
-    // (since cache is empty), but on the second call it 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_exit'))->fetchField(), $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('');
-    $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 normal cache and a cached page.');
-
-    // Test with aggressive cache. Exit should not fire since 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_exit'))->fetchField(), $calls, 'hook_exit not called with aggressive cache and a cached page.');
-
-    // Test with page cache cleared, 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_exit'))->fetchField(), $calls, 'hook_exit called with aggressive cache and no cached page.');
-  }
-}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
index 2221aaf..7044808 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
@@ -192,7 +192,9 @@ function assertSuccessfulDisableAndUninstall($module, $package = 'Core') {
     // Check that the appropriate hook was fired and the appropriate log
     // message appears.
     $this->assertText(t('hook_modules_disabled fired for @module', array('@module' => $module)));
-    $this->assertLogMessage('system', "%module module disabled.", array('%module' => $module), WATCHDOG_INFO);
+    if ($module != 'dblog') {
+      $this->assertLogMessage('system', "%module module disabled.", array('%module' => $module), WATCHDOG_INFO);
+    }
 
     //  Check that the module's database tables still exist.
     $this->assertModuleTablesExist($module);
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 484ee93..fbc936a 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -316,24 +316,6 @@ function hook_element_info_alter(&$type) {
 }
 
 /**
- * Perform cleanup tasks.
- *
- * 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.
- *
- * 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) {
-  watchdog('mymodule', 'Page was built for user %name.', array('%name' => user_format_name($GLOBALS['user'])), WATCHDOG_INFO);
-}
-
-/**
  * Perform necessary alterations to the JavaScript before it is presented on
  * the page.
  *
@@ -1339,8 +1321,6 @@ function hook_forms($form_id, $args) {
  *
  * This hook is not run on cached pages.
  *
- * @see hook_exit()
- *
  * Do not use this hook to add CSS/JS to pages, use hook_page_build() instead.
  *
  * @see hook_page_build()
diff --git a/core/modules/system/tests/modules/system_test/system_test.module b/core/modules/system/tests/modules/system_test/system_test.module
index 4494299..2f09c65 100644
--- a/core/modules/system/tests/modules/system_test/system_test.module
+++ b/core/modules/system/tests/modules/system_test/system_test.module
@@ -229,13 +229,6 @@ function system_test_init() {
 }
 
 /**
- * Implements hook_exit().
- */
-function system_test_exit() {
-  watchdog('system_test', 'hook_exit');
-}
-
-/**
  * Implements hook_system_info_alter().
  */
 function system_test_system_info_alter(&$info, $file, $type) {
diff --git a/core/update.php b/core/update.php
index 46d933b..a5d3698 100644
--- a/core/update.php
+++ b/core/update.php
@@ -39,7 +39,7 @@
  * Global flag indicating that update.php is being run.
  *
  * When this flag is set, various operations do not take place, such as invoking
- * hook_init() and hook_exit(), css/js preprocessing, and translation.
+ * hook_init(), css/js preprocessing, and translation.
  *
  * This constant is defined using define() instead of const so that PHP
  * versions older than 5.3 can display the proper PHP requirements instead of
