diff --git a/core/authorize.php b/core/authorize.php
index fd9e2f4..083a9f1 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -64,13 +64,15 @@ function authorize_access_allowed() {
 // *** Real work of the script begins here. ***
 
 require_once __DIR__ . '/includes/bootstrap.inc';
+require_once __DIR__ . '/includes/cache.inc';
 require_once __DIR__ . '/includes/common.inc';
+require_once __DIR__ . '/includes/database.inc';
 require_once __DIR__ . '/includes/file.inc';
 require_once __DIR__ . '/includes/module.inc';
 require_once __DIR__ . '/includes/ajax.inc';
 
 // Prepare a minimal bootstrap.
-drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
+drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL);
 $request = \Drupal::request();
 
 // We have to enable the user and system modules, even to check access and
diff --git a/core/core.services.yml b/core/core.services.yml
index 402d6ee..169cc5d 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -66,6 +66,11 @@ services:
     factory_method: get
     factory_service: cache_factory
     arguments: [path]
+  page_cache.internal:
+    class: Drupal\Core\PageCache\InternalPageCache
+    arguments: ['@cache.page', '@content_negotiation', '@config.factory', '@settings']
+  page_cache.policy:
+    class: Drupal\Core\PageCache\DefaultPageCachePolicy
   config.cachedstorage.storage:
     class: Drupal\Core\Config\FileStorage
     factory_class: Drupal\Core\Config\FileStorageFactory
@@ -507,7 +512,7 @@ services:
     class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
     tags:
       - { name: event_subscriber }
-    arguments: ['@language_manager']
+    arguments: ['@language_manager', '@page_cache.policy', '@page_cache.internal', '@config.factory']
     scope: request
   redirect_response_subscriber:
     class: Drupal\Core\EventSubscriber\RedirectResponseSubscriber
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index be8c695..29041d1 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -7,6 +7,7 @@
 use Drupal\Component\Utility\Timer;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\Url;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Database\Database;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
@@ -134,19 +135,14 @@
 const DRUPAL_BOOTSTRAP_KERNEL = 1;
 
 /**
- * Third bootstrap phase: try to serve a cached page.
- */
-const DRUPAL_BOOTSTRAP_PAGE_CACHE = 2;
-
-/**
  * Fourth bootstrap phase: load code for subsystems and modules.
  */
-const DRUPAL_BOOTSTRAP_CODE = 3;
+const DRUPAL_BOOTSTRAP_CODE = 2;
 
 /**
  * Final bootstrap phase: initialize language, path, theme, and modules.
  */
-const DRUPAL_BOOTSTRAP_FULL = 4;
+const DRUPAL_BOOTSTRAP_FULL = 3;
 
 /**
  * Role ID for anonymous users; should match what's in the "role" table.
@@ -753,6 +749,7 @@ function drupal_get_filename($type, $name, $filename = NULL) {
       if (!isset($dirs[$dir][$extension])) {
         $dirs[$dir][$extension] = TRUE;
         if (!function_exists('drupal_system_listing')) {
+          require_once __DIR__ . '/cache.inc';
           require_once __DIR__ . '/common.inc';
         }
         // Scan the appropriate directories for all files with the requested
@@ -787,62 +784,6 @@ function settings() {
 }
 
 /**
- * Gets the page cache cid for this request.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- *   The request for this page.
- *
- * @return string
- *   The cid for this request.
- */
-function drupal_page_cache_get_cid(Request $request) {
-  $cid_parts = array(
-    $request->getUri(),
-    \Drupal::service('content_negotiation')->getContentType($request),
-  );
-  return sha1(implode(':', $cid_parts));
-}
-
-/**
- * Retrieves the current page from the cache.
- *
- * Note: we do not serve cached pages to authenticated users, or to anonymous
- * users when $_SESSION is non-empty. $_SESSION may contain status messages
- * from a form submission, the contents of a shopping cart, or other user-
- * specific content that should not be cached and displayed to other users.
- *
- * @param \Symfony\Component\HttpFoundation\Request $request
- *   The request for this page.
- *
- * @return
- *   The cache object, if the page was found in the cache, NULL otherwise.
- */
-function drupal_page_get_cache(Request $request) {
-  if (drupal_page_is_cacheable()) {
-    return \Drupal::cache('page')->get(drupal_page_cache_get_cid($request));
-  }
-}
-
-/**
- * Determines the cacheability of the current page.
- *
- * @param $allow_caching
- *   Set to FALSE if you want to prevent this page to get cached.
- *
- * @return
- *   TRUE if the current page can be cached, FALSE otherwise.
- */
-function drupal_page_is_cacheable($allow_caching = NULL) {
-  $allow_caching_static = &drupal_static(__FUNCTION__, TRUE);
-  if (isset($allow_caching)) {
-    $allow_caching_static = $allow_caching;
-  }
-
-  return $allow_caching_static && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')
-    && !drupal_is_cli();
-}
-
-/**
  * Includes a file with the provided type and name.
  *
  * This prevents including a theme, engine, module, etc., more than once.
@@ -1045,106 +986,6 @@ function drupal_page_header() {
 }
 
 /**
- * Sets HTTP headers in preparation for a cached page response.
- *
- * The headers allow as much as possible in proxies and browsers without any
- * particular knowledge about the pages. Modules can override these headers
- * using drupal_add_http_header().
- *
- * If the request is conditional (using If-Modified-Since and If-None-Match),
- * and the conditions match those currently in the cache, a 304 Not Modified
- * response is sent.
- */
-function drupal_serve_page_from_cache(stdClass $cache, Response $response, Request $request) {
-  $config = \Drupal::config('system.performance');
-
-  // First half: we must determine if we should be returning a 304.
-
-  // Negotiate whether to use compression.
-  $page_compression = !empty($cache->data['page_compressed']) && extension_loaded('zlib');
-  $return_compressed = $page_compression && $request->server->has('HTTP_ACCEPT_ENCODING') && strpos($request->server->get('HTTP_ACCEPT_ENCODING'), 'gzip') !== FALSE;
-
-  // Get headers. Keys are lower-case.
-  $boot_headers = drupal_get_http_header();
-
-  foreach ($cache->data['headers'] as $name => $value) {
-    // In the case of a 304 response, certain headers must be sent, and the
-    // remaining may not (see RFC 2616, section 10.3.5).
-    $name_lower = strtolower($name);
-    if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($boot_headers[$name_lower])) {
-      $response->headers->set($name, $value);
-      unset($cache->data['headers'][$name]);
-    }
-  }
-
-  // If the client sent a session cookie, a cached copy will only be served
-  // to that one particular client due to Vary: Cookie. Thus, do not set
-  // max-age > 0, allowing the page to be cached by external proxies, when a
-  // session cookie is present unless the Vary header has been replaced.
-  $max_age = !$request->cookies->has(session_name()) || isset($boot_headers['vary']) ? $config->get('cache.page.max_age') : 0;
-  $response->headers->set('Cache-Control', 'public, max-age=' . $max_age);
-
-  // Entity tag should change if the output changes.
-  $response->setEtag($cache->created);
-
-  // See if the client has provided the required HTTP headers.
-  $if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
-  $if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
-
-  if ($if_modified_since && $if_none_match
-      && $if_none_match == $response->headers->get('etag') // etag must match
-      && $if_modified_since == $cache->created) {  // if-modified-since must match
-    $response->setStatusCode(304);
-    return;
-  }
-
-  // Second half: we're not returning a 304, so put in other headers.
-
-  // Send the remaining headers.
-  foreach ($cache->data['headers'] as $name => $value) {
-    $response->headers->set($name, $value);
-    drupal_add_http_header($name, $value);
-  }
-
-  $response->setLastModified(\DateTime::createFromFormat('U', $cache->created));
-
-  // HTTP/1.0 proxies does not support the Vary header, so prevent any caching
-  // by sending an Expires date in the past. HTTP/1.1 clients ignores the
-  // Expires header if a Cache-Control: max-age= directive is specified (see RFC
-  // 2616, section 14.9.3).
-  if (!$response->getExpires()) {
-    $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
-  }
-
-  // Allow HTTP proxies to cache pages for anonymous users without a session
-  // cookie. The Vary header is used to indicates the set of request-header
-  // fields that fully determines whether a cache is permitted to use the
-  // response to reply to a subsequent request for a given URL without
-  // revalidation.
-  if (!isset($boot_headers['vary']) && !settings()->get('omit_vary_cookie')) {
-    $response->setVary('Cookie', FALSE);
-  }
-
-  if ($page_compression) {
-    $response->setVary('accept-encoding', FALSE);
-    // If page_compression is enabled, the cache contains gzipped data.
-    if ($return_compressed) {
-      // $cache->data['body'] is already gzip'ed, so make sure
-      // zlib.output_compression does not compress it once more.
-      ini_set('zlib.output_compression', '0');
-      $response->headers->set('content-encoding', 'gzip');
-    }
-    else {
-      // The client does not support compression, so unzip the data in the
-      // cache. Strip the gzip header and run uncompress.
-      $cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8));
-    }
-  }
-
-  $response->setContent($cache->data['body']);
-}
-
-/**
  * Translates a string to the current language or to a given language.
  *
  * The t() function serves two purposes. First, at run-time it translates
@@ -1456,7 +1297,7 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
     }
 
     // Mark this page as being uncacheable.
-    drupal_page_is_cacheable(FALSE);
+    \Drupal::service('page_cache.policy')->cancel();
   }
 
   // Messages not set when DB connection fails.
@@ -1588,7 +1429,6 @@ function drupal_anonymous_user() {
  *   values:
  *   - DRUPAL_BOOTSTRAP_CONFIGURATION: Initializes configuration.
  *   - DRUPAL_BOOTSTRAP_KERNEL: Initalizes a kernel.
- *   - DRUPAL_BOOTSTRAP_PAGE_CACHE: Tries to serve a cached page.
  *   - DRUPAL_BOOTSTRAP_CODE: Loads code for subsystems and modules.
  *   - DRUPAL_BOOTSTRAP_FULL: Fully loads Drupal. Validates and fixes input
  *     data.
@@ -1601,7 +1441,6 @@ function drupal_bootstrap($phase = NULL) {
   static $phases = array(
     DRUPAL_BOOTSTRAP_CONFIGURATION,
     DRUPAL_BOOTSTRAP_KERNEL,
-    DRUPAL_BOOTSTRAP_PAGE_CACHE,
     DRUPAL_BOOTSTRAP_CODE,
     DRUPAL_BOOTSTRAP_FULL,
   );
@@ -1639,10 +1478,6 @@ function drupal_bootstrap($phase = NULL) {
           _drupal_bootstrap_kernel();
           break;
 
-        case DRUPAL_BOOTSTRAP_PAGE_CACHE:
-          _drupal_bootstrap_page_cache();
-          break;
-
         case DRUPAL_BOOTSTRAP_CODE:
           require_once __DIR__ . '/common.inc';
           _drupal_bootstrap_code();
@@ -1698,6 +1533,19 @@ function drupal_handle_request($test_only = FALSE) {
   $request = Request::createFromGlobals();
   \Drupal::getContainer()->set('request', $request);
 
+  // Apply the current cache policy to the request.
+  $cache_policy = \Drupal::service('page_cache.policy');
+  $cache_policy->applyToRequest($request);
+
+  // Attempt to serve a cached response from the internal page cache.
+  $use_internal_cache = \Drupal::config('system.performance')->get('cache.page.use_internal');
+  if ($use_internal_cache && $cache_policy->isCacheable()) {
+    $page_cache = \Drupal::service('page_cache.internal');
+    if ($page_cache->deliver($request)) {
+      exit();
+    }
+  }
+
   drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
 
   $response = $kernel->handle($request)->prepare($request)->send();
@@ -1835,53 +1683,6 @@ function _drupal_bootstrap_kernel() {
 }
 
 /**
- * Attempts to serve a page from the cache.
- */
-function _drupal_bootstrap_page_cache() {
-  global $user;
-
-  require_once __DIR__ . '/cache.inc';
-  require_once __DIR__ . '/database.inc';
-  // Check for a cache mode force from settings.php.
-  if (settings()->get('page_cache_without_database')) {
-    $cache_enabled = TRUE;
-  }
-  else {
-    $config = \Drupal::config('system.performance');
-    $cache_enabled = $config->get('cache.page.use_internal');
-  }
-
-  $request = Request::createFromGlobals();
-  // If there is no session cookie and cache is enabled (or forced), try
-  // to serve a cached page.
-  if (!$request->cookies->has(session_name()) && $cache_enabled) {
-    // Make sure there is a user object because its timestamp will be checked.
-    $user = drupal_anonymous_user();
-    // Get the page from the cache.
-    $cache = drupal_page_get_cache($request);
-    // If there is a cached page, display it.
-    if (is_object($cache)) {
-      $response = new Response();
-      $response->headers->set('X-Drupal-Cache', 'HIT');
-      // Restore the metadata cached with the page.
-      _current_path($cache->data['path']);
-      drupal_set_title($cache->data['title'], PASS_THROUGH);
-      date_default_timezone_set(drupal_get_user_timezone());
-
-      drupal_serve_page_from_cache($cache, $response, $request);
-
-      // We are done.
-      $response->prepare($request);
-      $response->send();
-      exit;
-    }
-    else {
-      drupal_add_http_header('X-Drupal-Cache', 'MISS');
-    }
-  }
-}
-
-/**
  * In a test environment, get the test db prefix and set it in $databases.
  */
 function _drupal_initialize_db_test_prefix() {
diff --git a/core/includes/cache.inc b/core/includes/cache.inc
index b39b7ff..9a94ef4 100644
--- a/core/includes/cache.inc
+++ b/core/includes/cache.inc
@@ -23,6 +23,9 @@
  * @return \Drupal\Core\Cache\CacheBackendInterface
  *   The cache object associated with the specified bin.
  *
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0 by
+ *   https://drupal.org/node/2184985. Use \Drupal::caches().
+ *
  * @see \Drupal\Core\Cache\CacheBackendInterface
  */
 function cache($bin = 'cache') {
@@ -42,7 +45,8 @@ function cache($bin = 'cache') {
  * @param array $tags
  *   The list of tags to invalidate cache items for.
  *
- * @deprecated 8.x
+ * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0 by
+ *   https://drupal.org/node/2184985.
  *   Use \Drupal\Core\Cache\Cache::invalidateTags().
  */
 function cache_invalidate_tags(array $tags) {
diff --git a/core/includes/common.inc b/core/includes/common.inc
index b97cc5e..756b5d8 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3045,6 +3045,8 @@ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
  * Loads code for subsystems and modules, and registers stream wrappers.
  */
 function _drupal_bootstrap_code() {
+  require_once __DIR__ . '/cache.inc';
+  require_once __DIR__ . '/database.inc';
   require_once __DIR__ . '/../../' . settings()->get('path_inc', 'core/includes/path.inc');
   require_once __DIR__ . '/module.inc';
   require_once __DIR__ . '/theme.inc';
@@ -3113,70 +3115,6 @@ function _drupal_bootstrap_full($skip = FALSE) {
 }
 
 /**
- * Stores the current page in the cache.
- *
- * If page_compression is enabled, a gzipped version of the page is stored in
- * the cache to avoid compressing the output on each request. The cache entry
- * is unzipped in the relatively rare event that the page is requested by a
- * client without gzip support.
- *
- * Page compression requires the PHP zlib extension
- * (http://php.net/manual/ref.zlib.php).
- *
- * @param $body
- *   The response body.
- * @return
- *   The cached object or NULL if the page cache was not set.
- *
- * @see drupal_page_header()
- */
-function drupal_page_set_cache(Response $response, Request $request) {
-  if (drupal_page_is_cacheable()) {
-
-    // Check if the current page may be compressed.
-    $page_compressed = \Drupal::config('system.performance')->get('response.gzip') && extension_loaded('zlib');
-
-    $cache = (object) array(
-      'cid' => drupal_page_cache_get_cid($request),
-      'data' => array(
-        'path' => $request->attributes->get('_system_path'),
-        'body' => $response->getContent(),
-        'title' => drupal_get_title(),
-        'headers' => array(),
-        // We need to store whether page was compressed or not,
-        // because by the time it is read, the configuration might change.
-        'page_compressed' => $page_compressed,
-      ),
-      'tags' => array('content' => TRUE) + drupal_cache_tags_page_get($response),
-      'expire' => Cache::PERMANENT,
-      'created' => REQUEST_TIME,
-    );
-
-    $cache->data['headers'] = $response->headers->all();
-
-    // Hack: exclude the x-drupal-cache header; it may make it in here because
-    // of awkwardness in how we defer sending it over in _drupal_page_get_cache.
-    if (isset($cache->data['headers']['x-drupal-cache'])) {
-      unset($cache->data['headers']['x-drupal-cache']);
-    }
-
-     // Use the actual timestamp from an Expires header, if available.
-    if ($date = $response->getExpires()) {
-      $date = DrupalDateTime::createFromDateTime($date);
-      $cache->expire = $date->getTimestamp();
-    }
-
-    if ($cache->data['body']) {
-      if ($page_compressed) {
-        $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
-      }
-      cache('page')->set($cache->cid, $cache->data, $cache->expire, $cache->tags);
-    }
-    return $cache;
-  }
-}
-
-/**
  * This function is kept only for backward compatibility.
  *
  * @see \Drupal\Core\SystemListing::scan().
diff --git a/core/includes/install.inc b/core/includes/install.inc
index f3fbdf0..1695c88 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -559,6 +559,7 @@ function install_ensure_config_directory($type) {
  */
 function drupal_verify_profile($install_state) {
   include_once __DIR__ . '/file.inc';
+  include_once __DIR__ . '/cache.inc';
   include_once __DIR__ . '/common.inc';
 
   $profile = $install_state['parameters']['profile'];
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index bb1c6e6..1c7b6ae 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -74,6 +74,7 @@ function drupal_get_complete_schema($rebuild = FALSE) {
       // Load the .install files to get hook_schema.
       \Drupal::moduleHandler()->loadAllIncludes('install');
 
+      require_once __DIR__ . '/cache.inc';
       require_once __DIR__ . '/common.inc';
       // Invoke hook_schema for all modules.
       foreach (\Drupal::moduleHandler()->getImplementations('schema') as $module) {
diff --git a/core/includes/session.inc b/core/includes/session.inc
index fd2b1ec..7ecbaf4 100644
--- a/core/includes/session.inc
+++ b/core/includes/session.inc
@@ -254,7 +254,7 @@ function drupal_session_initialize() {
     // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
     drupal_session_start();
     if ($user->isAuthenticated() || !empty($_SESSION)) {
-      drupal_page_is_cacheable(FALSE);
+      \Drupal::service('page_cache.policy')->cancel();
     }
   }
   else {
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 8456eee..0571ee6 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -26,6 +26,7 @@ function _drupal_maintenance_theme() {
 
   require_once DRUPAL_ROOT . '/' . settings()->get('path_inc', 'core/includes/path.inc');
   require_once __DIR__ . '/theme.inc';
+  require_once __DIR__ . '/cache.inc';
   require_once __DIR__ . '/common.inc';
   require_once __DIR__ . '/unicode.inc';
   require_once __DIR__ . '/file.inc';
diff --git a/core/includes/utility.inc b/core/includes/utility.inc
index 023ecb0..a12e734 100644
--- a/core/includes/utility.inc
+++ b/core/includes/utility.inc
@@ -40,16 +40,10 @@ function drupal_rebuild() {
   restore_error_handler();
   restore_exception_handler();
 
-  // drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL) will build a new kernel. This
-  // comes before DRUPAL_BOOTSTRAP_PAGE_CACHE.
   PhpStorageFactory::get('service_container')->deleteAll();
   PhpStorageFactory::get('twig')->deleteAll();
 
-  // Disable the page cache.
-  drupal_page_is_cacheable(FALSE);
-
-  // Bootstrap up to where caches exist and clear them.
-  drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL);
   foreach (Cache::getBins() as $bin) {
     $bin->deleteAll();
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 8e4596f..f6af888 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -7,8 +7,12 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\PageCache\InternalPageCacheInterface;
+use Drupal\Core\PageCache\PageCachePolicyInterface;
 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -22,18 +26,49 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
   /**
    * The LanguageManager object for retrieving the correct language code.
    *
-   * @var LanguageManager
+   * @var \Drupal\Core\Language\LanguageManager
    */
   protected $languageManager;
 
   /**
+   * The PageCachePolicy object necessary to decide whether a page is cacheable.
+   *
+   * @var \Drupal\Core\PageCache\PageCachePolicyInterface
+   */
+  protected $pageCachePolicy;
+
+  /**
+   * The InternalPageCache object used to store cached pages.
+   *
+   * @var \Drupal\Core\PageCache\InternalPageCacheInterface
+   */
+  protected $internalPageCache;
+
+  /**
+   * A config object for the system performance configuration.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
    * Constructs a FinishResponseSubscriber object.
    *
-   * @param LanguageManager $language_manager
-   *  The LanguageManager object for retrieving the correct language code.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The LanguageManager object for retrieving the correct language code.
+   * @param \Drupal\Core\PageCache\PageCachePolicyInterface $page_cache_policy
+   *   The PageCachePolicy object necessary to decide whether a page is
+   *   cacheable.
+   * @param \Drupal\Core\PageCache\InternalPageCacheInterface $internal_page_cache
+   *   The InternalPageCache object used to store cached pages.
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   A config factory for retrieving required config objects.
    */
-  public function __construct(LanguageManager $language_manager) {
+  public function __construct(LanguageManager $language_manager, PageCachePolicyInterface $page_cache_policy, InternalPageCacheInterface $internal_page_cache, ConfigFactory $config_factory) {
     $this->languageManager = $language_manager;
+    $this->pageCachePolicy = $page_cache_policy;
+    $this->internalPageCache = $internal_page_cache;
+    $this->config = $config_factory->get('system.performance');
   }
 
   /**
@@ -57,6 +92,15 @@ public function onRespond(FilterResponseEvent $event) {
     // Set the Content-language header.
     $response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->id);
 
+    // A cache-control header is added automatically during construction of the
+    // Response object. Therefore it is necessary to compare the actual value of
+    // the header with the default value in order to determine whether it was
+    // set explicitely throughout the request. Note that this header also is set
+    // whenever one of the Etag, Expires and Last-Modified headers is set. See
+    // also:
+    // \Symfony\Component\HttpFoundation\ResponseHeaderBag::computeCacheControlValue()
+    $has_custom_cache_control = ($response->headers->get('Cache-Control') != 'no-cache');
+
     // Because pages are highly dynamic, set the last-modified time to now
     // since the page is in fact being regenerated right now.
     // @todo Remove this and use a more intelligent default so that HTTP
@@ -100,9 +144,15 @@ public function onRespond(FilterResponseEvent $event) {
       $response->headers->set($name, $value, FALSE);
     }
 
-    $max_age = \Drupal::config('system.performance')->get('cache.page.max_age');
-    if ($max_age > 0 && ($cache = drupal_page_set_cache($response, $request))) {
-      drupal_serve_page_from_cache($cache, $response, $request);
+    $max_age = $this->config->get('cache.page.max_age');
+    $this->pageCachePolicy->applyToResponse($response, $request);
+    if ($max_age > 0 && $this->pageCachePolicy->isCacheable()) {
+      // Only record the Cache-Control header along with the cache object when
+      // it was explicitely set during the request.
+      if (!$has_custom_cache_control) {
+        $response->headers->remove('Cache-Control');
+      }
+      $this->internalPageCache->record($response, $request);
     }
     else {
       $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
diff --git a/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
new file mode 100644
index 0000000..d14b735
--- /dev/null
+++ b/core/lib/Drupal/Core/PageCache/DefaultPageCachePolicy.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\PageCache\DefaultPageCachePolicy
+ */
+
+namespace Drupal\Core\PageCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Page caching service based on the internal cache.
+ */
+class DefaultPageCachePolicy implements PageCachePolicyInterface {
+  /**
+   * @var bool
+   */
+  protected $excluded;
+
+  /**
+   * @var bool
+   */
+  protected $canceled;
+
+  /**
+   * Prevent that a page gets saved to the cache.
+   */
+  public function cancel() {
+    $this->canceled = TRUE;
+  }
+
+  /**
+   * Returns TRUE when page caching is not possible due to request properties.
+   */
+  public function isExcluded() {
+    return !empty($this->excluded);
+  }
+
+  /**
+   * Returns TRUE when page caching was canceled midways throug a page build.
+   */
+  public function isCanceled() {
+    return !empty($this->canceled);
+  }
+
+  /**
+   * Return TRUE when the page caching is allowed for the current request.
+   */
+  public function isCacheable() {
+    return !$this->isExcluded() && !$this->isCanceled();
+  }
+
+  /**
+   * Apply the default caching policy before a request is handled.
+   */
+  public function applyToRequest(Request $request) {
+    $this->excludeUncacheableHTTPMethod($request);
+    $this->excludeCLIRequest($request);
+    $this->excludeOpenSession($request);
+  }
+
+  /**
+   * Apply page caching possibly after a response was built.
+   */
+  public function applyToResponse(Response $response, Request $request) {
+  }
+
+  /**
+   * Only allow GET and HEAD requests.
+   */
+  protected function excludeUncacheableHTTPMethod(Request $request) {
+    if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
+      $this->excluded = TRUE;
+    }
+  }
+
+  /**
+   * Exclude page requests when drupal was invoked by the command line.
+   */
+  protected function excludeCLIRequest(Request $request) {
+    if (drupal_is_cli()) {
+      $this->excluded = TRUE;
+    }
+  }
+
+  /**
+   * Exclude page requests of users with an open session.
+   *
+   * Do not serve cached pages to authenticated users, or to anonymous users
+   * when $_SESSION is non-empty. $_SESSION may contain status messages from a
+   * form submission, the contents of a shopping cart, or other user- specific
+   * content that should not be cached and displayed to other users.
+   */
+  protected function excludeOpenSession(Request $request) {
+    if ($request->cookies->has(session_name())) {
+      $this->excluded = TRUE;
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/PageCache/InternalPageCache.php b/core/lib/Drupal/Core/PageCache/InternalPageCache.php
new file mode 100644
index 0000000..2820577
--- /dev/null
+++ b/core/lib/Drupal/Core/PageCache/InternalPageCache.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\PageCache\InternalPageCache.
+ */
+
+namespace Drupal\Core\PageCache;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\ContentNegotiation;
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Component\Utility\Settings;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Page caching service based on the internal cache.
+ */
+class InternalPageCache implements InternalPageCacheInterface {
+  /**
+   * Cache backend for the page cache.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * Content negotiation service.
+   *
+   * @var \Drupal\Core\ContentNegotiation
+   */
+  protected $negotiation;
+
+  /**
+   * A config object for the system performance configuration.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+
+  /**
+   * The settings instance.
+   *
+   * @var \Drupal\Component\Utility\Settings
+   */
+  protected $settings;
+
+  /**
+   * Constructs a new InternalPageCache object.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend to use.
+   * @param \Drupal\Core\ContentNegotiation $negotiation
+   *   The content negotiation service.
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The configuration factory service.
+   * @param \Drupal\Component\Utility\Settings $settings
+   *   The settings instance.
+   */
+  public function __construct(CacheBackendInterface $cache, ContentNegotiation $negotiation, ConfigFactory $config_factory, Settings $settings) {
+    $this->cache = $cache;
+    $this->negotiation = $negotiation;
+    $this->config = $config_factory->get('system.performance');
+    $this->settings = $settings;
+  }
+
+  /**
+   * Attempt to deliver the given request from the page cache.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request for this page.
+   *
+   * @return bool
+   *   TRUE when the page was delivered from the cache.
+   */
+  public function deliver(Request $request) {
+    // Get the page from the cache.
+    $cache = $this->cache->get($this->getCid($request));
+    if (is_object($cache)) {
+      $response = new Response();
+      $response->headers->set('X-Drupal-Cache', 'HIT');
+      date_default_timezone_set(drupal_get_user_timezone());
+
+      $this->populateResponse($cache, $response, $request);
+
+      $response->prepare($request);
+      $response->send();
+      return TRUE;
+    }
+  }
+
+  /**
+   * Save a built response in the page cache.
+   *
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   *   The response object.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request for this page.
+   */
+  public function record(Response $response, Request $request) {
+    $cache = $this->set($response, $request);
+    $response->headers->set('X-Drupal-Cache', 'MISS');
+    $this->populateResponse($cache, $response, $request);
+  }
+
+  /**
+   * Gets the page cache cid for this request.
+   *
+   * @todo Move this into a dedicated service.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request for this page.
+   *
+   * @return string
+   *   The cid for this request.
+   */
+  protected function getCid(Request $request) {
+    $cid_parts = array(
+      $request->getUri(),
+      $this->negotiation->getContentType($request),
+    );
+    return sha1(implode(':', $cid_parts));
+  }
+
+  /**
+   * Sets HTTP headers in preparation for a cached page response.
+   *
+   * The headers allow caching as much as possible in proxies and browsers
+   * without any particular knowledge about the pages. Controllers and event
+   * listeners may supply additional headers by adding them directly to the
+   * response object.
+   *
+   * If the request is conditional (using If-Modified-Since and If-None-Match),
+   * and the conditions match those currently in the cache, a 304 Not Modified
+   * response is sent.
+   */
+  protected function populateResponse(\stdClass $cache, Response $response, Request $request) {
+    // First half: we must determine if we should be returning a 304.
+
+    // Negotiate whether to use compression.
+    $page_compression = !empty($cache->data['page_compressed']) && extension_loaded('zlib');
+    $return_compressed = $page_compression && $request->server->has('HTTP_ACCEPT_ENCODING') && strpos($request->server->get('HTTP_ACCEPT_ENCODING'), 'gzip') !== FALSE;
+
+    foreach ($cache->data['headers'] as $name => $value) {
+      // In the case of a 304 response, certain headers must be sent, and the
+      // remaining may not (see RFC 2616, section 10.3.5).
+      $name_lower = strtolower($name);
+      if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !$response->headers->has($name)) {
+        $response->headers->set($name, $value);
+        unset($cache->data['headers'][$name]);
+      }
+    }
+
+    // If the client sent a session cookie, a cached copy will only be served
+    // to that one particular client due to Vary: Cookie. Thus, do not set
+    // max-age > 0, allowing the page to be cached by external proxies, when a
+    // session cookie is present unless the Vary header has been replaced.
+    $max_age = !$request->cookies->has(session_name()) || $response->hasVary() ? $this->config->get('cache.page.max_age') : 0;
+    $response->setPublic();
+    $response->setMaxAge($max_age);
+
+    // Entity tag should change if the output changes.
+    $response->setEtag($cache->created);
+
+    // See if the client has provided the required HTTP headers.
+    $if_modified_since = $request->server->has('HTTP_IF_MODIFIED_SINCE') ? strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE')) : FALSE;
+    $if_none_match = $request->server->has('HTTP_IF_NONE_MATCH') ? stripslashes($request->server->get('HTTP_IF_NONE_MATCH')) : FALSE;
+
+    if ($if_modified_since && $if_none_match
+      && $if_none_match == $response->headers->get('etag') // etag must match
+      && $if_modified_since == $cache->created) {  // if-modified-since must match
+        $response->setStatusCode(304);
+        return;
+      }
+
+    // Second half: we're not returning a 304, so put in other headers.
+
+    // Send the remaining headers.
+    foreach ($cache->data['headers'] as $name => $value) {
+      $response->headers->set($name, $value);
+    }
+
+    $response->setLastModified(\DateTime::createFromFormat('U', $cache->created));
+
+    // HTTP/1.0 proxies does not support the Vary header, so prevent any caching
+    // by sending an Expires date in the past. HTTP/1.1 clients ignores the
+    // Expires header if a Cache-Control: max-age= directive is specified (see RFC
+    // 2616, section 14.9.3).
+    if (!$response->getExpires()) {
+      $response->setExpires(\DateTime::createFromFormat('j-M-Y H:i:s T', '19-Nov-1978 05:00:00 GMT'));
+    }
+
+    // Allow HTTP proxies to cache pages for anonymous users without a session
+    // cookie. The Vary header is used to indicates the set of request-header
+    // fields that fully determines whether a cache is permitted to use the
+    // response to reply to a subsequent request for a given URL without
+    // revalidation.
+    if (!$response->hasVary() && !$this->settings->get('omit_vary_cookie')) {
+      $response->setVary('Cookie', FALSE);
+    }
+
+    if ($page_compression) {
+      $response->setVary('accept-encoding', FALSE);
+      // If page_compression is enabled, the cache contains gzipped data.
+      if ($return_compressed) {
+        // $cache->data['body'] is already gzip'ed, so make sure
+        // zlib.output_compression does not compress it once more.
+        ini_set('zlib.output_compression', '0');
+        $response->headers->set('content-encoding', 'gzip');
+      }
+      else {
+        // The client does not support compression, so unzip the data in the
+        // cache. Strip the gzip header and run uncompress.
+        $cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8));
+      }
+    }
+
+    $response->setContent($cache->data['body']);
+  }
+
+  /**
+   * Stores the current page in the cache.
+   *
+   * If page_compression is enabled, a gzipped version of the page is stored in
+   * the cache to avoid compressing the output on each request. The cache entry
+   * is unzipped in the relatively rare event that the page is requested by a
+   * client without gzip support.
+   *
+   * Page compression requires the PHP zlib extension
+   * (http://php.net/manual/ref.zlib.php).
+   *
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   *   The response object.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request for this page.
+   *
+   * @return \stdObject|null
+   *   The cached object or NULL if the page cache was not set.
+   */
+  protected function set(Response $response, Request $request) {
+    // Check if the current page may be compressed.
+    $page_compressed = $this->config->get('response.gzip') && extension_loaded('zlib');
+
+    $cache = (object) array(
+      'cid' => $this->getCid($request),
+      'data' => array(
+        'body' => $response->getContent(),
+        'headers' => array(),
+        // We need to store whether page was compressed or not,
+        // because by the time it is read, the configuration might change.
+        'page_compressed' => $page_compressed,
+      ),
+      'tags' => array('content' => TRUE) + drupal_cache_tags_page_get($response),
+      'expire' => Cache::PERMANENT,
+      'created' => REQUEST_TIME,
+    );
+
+    $cache->data['headers'] = $response->headers->all();
+
+    // Use the actual timestamp from an Expires header, if available.
+    if ($date = $response->getExpires()) {
+      $date = DrupalDateTime::createFromDateTime($date);
+      $cache->expire = $date->getTimestamp();
+    }
+
+    if ($cache->data['body']) {
+      if ($page_compressed) {
+        $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
+      }
+      $this->cache->set($cache->cid, $cache->data, $cache->expire, $cache->tags);
+    }
+    return $cache;
+  }
+}
diff --git a/core/lib/Drupal/Core/PageCache/InternalPageCacheInterface.php b/core/lib/Drupal/Core/PageCache/InternalPageCacheInterface.php
new file mode 100644
index 0000000..90169e4
--- /dev/null
+++ b/core/lib/Drupal/Core/PageCache/InternalPageCacheInterface.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\PageCache\InternalPageCacheInterface.
+ */
+
+namespace Drupal\Core\PageCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Interface for page caching services based on the internal cache.
+ */
+interface InternalPageCacheInterface {
+
+  /**
+   * Attempt to deliver the given request from the page cache.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request for this page.
+   *
+   * @return bool
+   *   TRUE when the page was delivered from the cache.
+   */
+  public function deliver(Request $request);
+
+  /**
+   * Save a built response in the page cache.
+   *
+   * @param \Symfony\Component\HttpFoundation\Response $response
+   *   The response object.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request for this page.
+   */
+  public function record(Response $response, Request $request);
+}
diff --git a/core/lib/Drupal/Core/PageCache/PageCachePolicyInterface.php b/core/lib/Drupal/Core/PageCache/PageCachePolicyInterface.php
new file mode 100644
index 0000000..a9834b3
--- /dev/null
+++ b/core/lib/Drupal/Core/PageCache/PageCachePolicyInterface.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\PageCache\PageCachePolicy
+ */
+
+namespace Drupal\Core\PageCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Defines the interface for page caching policy implementations.
+ *
+ * During a request the page cache policy is applied exactly twice. The first
+ * time just before attempting to deliver a cached page and the second time
+ * before a page is saved to the page cache.
+ *
+ * Throughout the request, client code may also cancel page caching. This is
+ * necessary for example when a page contains messages.
+ *
+ * If page caching has been disabled either by the policy rules or when third
+ * party code canceled the caching, the markup will not be saved to the page
+ * cache.
+ */
+interface PageCachePolicyInterface {
+  /**
+   * Prevent that a page gets saved to the cache.
+   */
+  public function cancel();
+
+  /**
+   * Returns TRUE when page caching was disabled by the policy rules.
+   */
+  public function isExcluded();
+
+  /**
+   * Returns TRUE when page caching was canceled midways througout a page build.
+   */
+  public function isCanceled();
+
+  /**
+   * Return TRUE when the page caching is allowed for the current request.
+   */
+  public function isCacheable();
+
+  /**
+   * Apply the caching policy rules before a request is handled.
+   */
+  public function applyToRequest(Request $request);
+
+  /**
+   * Apply page caching policy rules after a response was built.
+   */
+  public function applyToResponse(Response $response, Request $request);
+}
diff --git a/core/modules/statistics/statistics.php b/core/modules/statistics/statistics.php
index 59675f1..b27ea13 100644
--- a/core/modules/statistics/statistics.php
+++ b/core/modules/statistics/statistics.php
@@ -11,7 +11,8 @@
 // Load the Drupal bootstrap.
 require_once dirname(dirname(__DIR__)) . '/vendor/autoload.php';
 require_once dirname(dirname(__DIR__)) . '/includes/bootstrap.inc';
-drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
+require_once dirname(dirname(__DIR__)) . '/includes/database.inc';
+drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL);
 
 if (\Drupal::config('statistics.settings')->get('count_content_views')) {
   $nid = filter_input(INPUT_POST, 'nid', FILTER_VALIDATE_INT);
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/PageCache/ToolbarPageCachePolicy.php b/core/modules/toolbar/lib/Drupal/toolbar/PageCache/ToolbarPageCachePolicy.php
new file mode 100644
index 0000000..c48d47e
--- /dev/null
+++ b/core/modules/toolbar/lib/Drupal/toolbar/PageCache/ToolbarPageCachePolicy.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\toolbar\PageCache\ToolbarPageCachePolicy
+ */
+
+namespace Drupal\toolbar\PageCache;
+
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\PageCache\DefaultPageCachePolicy;
+
+/**
+ * Page caching service based on the internal cache.
+ */
+class ToolbarPageCachePolicy extends DefaultPageCachePolicy {
+  /**
+   * Do *not* exclude users with an open session.
+   */
+  protected function excludeOpenSession(Request $request) {
+    // Intentionally left blank.
+  }
+}
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
index 3a7eb3c..772f972 100644
--- a/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
+++ b/core/modules/toolbar/lib/Drupal/toolbar/Routing/ToolbarController.php
@@ -27,6 +27,16 @@ public function subtreesJsonp() {
     $subtrees = toolbar_get_rendered_subtrees();
     $response = new JsonResponse($subtrees);
     $response->setCallback('Drupal.toolbar.setSubtrees.resolve');
+
+    // The Expires HTTP header is the heart of the client-side HTTP caching. The
+    // additional server-side page cache only takes effect when the client
+    // accesses the callback URL again (e.g., after clearing the browser cache or
+    // when force-reloading a Drupal page).
+    $max_age = 3600 * 24 * 365;
+    $response->setExpires(\DateTime::createFromFormat('U', REQUEST_TIME + $max_age));
+    $response->setPrivate();
+    $response->setMaxAge($max_age);
+
     return $response;
   }
 
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index d91527b..9240be7 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -114,35 +114,18 @@ function toolbar_element_info() {
  */
 function _toolbar_initialize_page_cache() {
   $GLOBALS['conf']['system.performance']['cache']['page']['enabled'] = TRUE;
-  drupal_page_is_cacheable(TRUE);
+
+  // Replace page cache policy with our own implementation.
+  $toolbar_page_cache_policy = \Drupal::service('toolbar.page_cache.policy');
+  \Drupal::getContainer()->set('page_cache.policy', $toolbar_page_cache_policy);
 
   // If we have a cache, serve it.
-  // @see _drupal_bootstrap_page_cache()
   $request = \Drupal::request();
-  $cache = drupal_page_get_cache($request);
-  if (is_object($cache)) {
-    $response = new Response();
-    $response->headers->set('X-Drupal-Cache', 'HIT');
-    date_default_timezone_set(drupal_get_user_timezone());
-
-    drupal_serve_page_from_cache($cache, $response, $request);
-
-    $response->prepare($request);
-    $response->send();
-    // We are done.
-    exit;
+  $toolbar_page_cache_policy->applyToRequest($request);
+  // Attempt to serve the page from the cache.
+  if (\Drupal::service('page_cache.internal')->deliver($request)) {
+    exit();
   }
-
-  // Otherwise, create a new page response (that will be cached).
-  drupal_add_http_header('X-Drupal-Cache', 'MISS');
-
-  // The Expires HTTP header is the heart of the client-side HTTP caching. The
-  // additional server-side page cache only takes effect when the client
-  // accesses the callback URL again (e.g., after clearing the browser cache or
-  // when force-reloading a Drupal page).
-  $max_age = 3600 * 24 * 365;
-  drupal_add_http_header('Expires', gmdate(DATE_RFC1123, REQUEST_TIME + $max_age));
-  drupal_add_http_header('Cache-Control', 'private, max-age=' . $max_age);
 }
 
 /**
diff --git a/core/modules/toolbar/toolbar.services.yml b/core/modules/toolbar/toolbar.services.yml
index 7f26968..0cce863 100644
--- a/core/modules/toolbar/toolbar.services.yml
+++ b/core/modules/toolbar/toolbar.services.yml
@@ -6,3 +6,5 @@ services:
     factory_method: get
     factory_service: cache_factory
     arguments: [toolbar]
+  toolbar.page_cache.policy:
+    class: Drupal\toolbar\PageCache\ToolbarPageCachePolicy
diff --git a/core/update.php b/core/update.php
index 6491afc..9346e15 100644
--- a/core/update.php
+++ b/core/update.php
@@ -294,6 +294,7 @@ function update_task_list($active = NULL) {
 // reaching the PHP memory limit.
 require_once __DIR__ . '/includes/bootstrap.inc';
 require_once __DIR__ . '/includes/update.inc';
+require_once __DIR__ . '/includes/cache.inc';
 require_once __DIR__ . '/includes/common.inc';
 require_once __DIR__ . '/includes/file.inc';
 require_once __DIR__ . '/includes/unicode.inc';
@@ -333,7 +334,7 @@ function update_task_list($active = NULL) {
 \Drupal::getContainer()->set('request', $request);
 
 // Determine if the current user has access to run update.php.
-drupal_bootstrap(DRUPAL_BOOTSTRAP_PAGE_CACHE);
+drupal_bootstrap(DRUPAL_BOOTSTRAP_KERNEL);
 
 require_once DRUPAL_ROOT . '/' . settings()->get('session_inc', 'core/includes/session.inc');
 drupal_session_initialize();
