diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 3e3853a..84c2cf8 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2559,7 +2559,7 @@ function module_hook($module, $hook) {
  * @return Drupal\Core\KeyValueStore\KeyValueStoreInterface
  */
 function state() {
-  return drupal_container()->get('keyvalue')->get('state');
+  return drupal_container()->get('state');
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php
new file mode 100644
index 0000000..28c9026
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheCollector.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Cache\CacheCollector.
+ */
+
+namespace Drupal\Core\Cache;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+
+/**
+ * Default implementation for CacheCollectorInterface.
+ *
+ * By default, the class accounts for caches where calling functions might
+ * request keys that won't exist even after a cache rebuild. This
+ * prevents situations where a cache rebuild would be triggered over and over
+ * due to a 'missing' item. These cases are stored internally as a value of
+ * NULL. This means that the CacheCollector::get() method must be overridden if
+ * caching data where the values can legitimately be NULL, and where
+ * CacheCollector->has() needs to correctly return (equivalent to
+ * array_key_exists() vs. isset()). This should not be necessary in the majority
+ * of cases.
+ *
+ * Classes extending this class must override at least the
+ * CacheCollector::resolveCacheMiss() method to have a working implementation.
+ */
+abstract class CacheCollector implements CacheCollectorInterface {
+
+  /**
+   * A cid to pass to cache()->set() and cache()->get().
+   *
+   * @var string
+   */
+  protected $cid;
+
+  /**
+   * A tags array to pass to cache()->set().
+   *
+   * @var array
+   */
+  protected $tags;
+
+  /**
+   * The cache backend that should be used.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * The lock backend that should be used.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
+  /**
+   * An array of keys to add to the cache on service termination.
+   *
+   * @var array
+   */
+  protected $keysToPersist = array();
+
+  /**
+   * An array of keys to remove from the cache on service termination.
+   *
+   * @var array
+   */
+  protected $keysToRemove = array();
+
+  /**
+   * Storage for the data itself.
+   *
+   * @var array
+   */
+  protected $storage = array();
+
+  /**
+   * Constructs a CacheArray object.
+   *
+   * @param string $cid
+   *   The cid for the array being cached.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend.
+   * @param array $tags
+   *   (optional) The tags to specify for the cache item.
+   */
+  public function __construct($cid, CacheBackendInterface $cache, \Drupal\Core\Lock\LockBackendInterface $lock, $tags = array()) {
+    $this->cid = $cid;
+    $this->cache = $cache;
+    $this->tags = $tags;
+    $this->lock = $lock;
+
+    if ($cached = $this->cache->get($this->cid)) {
+     $this->storage = $cached->data;
+    }
+  }
+
+  /**
+   * Implements CacheCollectorInterface::has().
+   */
+  public function has($key) {
+    return $this->get($key) !== NULL;
+  }
+
+  /**
+   * Implements CacheCollectorInterface::get().
+   */
+  public function get($key) {
+    if (isset($this->storage[$key]) || array_key_exists($key, $this->storage)) {
+      return $this->storage[$key];
+    }
+    else {
+      return $this->resolveCacheMiss($key);
+    }
+  }
+
+  /**
+   * Implements CacheCollectorInterface::set().
+   *
+   * This is not persisted by default. In practice this means that setting a
+   * value will only apply while the object is in scope and will not be written
+   * back to the persistent cache. This follows a similar pattern to static vs.
+   * persistent caching in procedural code. Extending classes may wish to alter
+   * this behavior, for example by adding a call to persist().
+   */
+  public function set($key, $value) {
+    $this->storage[$key] = $value;
+    // The key might have been marked for deletion.
+    unset($this->keysToRemove[$key]);
+
+  }
+
+  /**
+   * Implements CacheCollectorInterface::delete().
+   */
+  public function delete($key) {
+    unset($this->storage[$key]);
+    $this->keysToRemove[$key] = $key;
+    // The key might have been marked for persisting.
+    unset($this->keysToPersist[$key]);
+  }
+
+  /**
+   * Flags an offset value to be written to the persistent cache.
+   *
+   * @param $key
+   *   The key that was request.
+   * @param $persist
+   *   Optional boolean to specify whether the offset should be persisted or
+   *   not, defaults to TRUE. When called with $persist = FALSE the offset will
+   *   be unflagged so that it will not written at the end of the request.
+   */
+  protected function persist($key, $persist = TRUE) {
+    $this->keysToPersist[$key] = $persist;
+  }
+
+  /**
+   * Resolves a cache miss.
+   *
+   * When an offset is not found in the object, this is treated as a cache
+   * miss. This method allows classes using this implementatio to look up the
+   * actual value and allow it to be cached.
+   *
+   * @param $key
+   *   The offset that was requested.
+   *
+   * @return
+   *   The value of the offset, or NULL if no value was found.
+   */
+  abstract protected function resolveCacheMiss($key);
+
+  /**
+   * Writes a value to the persistent cache immediately.
+   *
+   * @param $data
+   *   The data to write to the persistent cache.
+   * @param $lock
+   *   Whether to acquire a lock before writing to cache.
+   */
+  protected function updateCache($lock = TRUE) {
+    $data = array();
+    foreach ($this->keysToPersist as $offset => $persist) {
+      if ($persist) {
+        $data[$offset] = $this->storage[$offset];
+      }
+    }
+    if (empty($data)) {
+      return;
+    }
+
+    // Lock cache writes to help avoid stampedes.
+    // To implement locking for cache misses, override __construct().
+    $lock_name = $this->cid . ':' . __CLASS__;
+    if (!$lock || $this->lock->acquire($lock_name)) {
+      if ($cached = $this->cache->get($this->cid)) {
+        $data = array_merge($cached->data, $data);
+      }
+      // Remove keys marked for deletion.
+      foreach ($this->keysToRemove as $delete_key) {
+        unset($data[$delete_key]);
+      }
+      $this->cache->set($this->cid, $data, CacheBackendInterface::CACHE_PERMANENT, $this->tags);
+      if ($lock) {
+        $this->lock->release($lock_name);
+      }
+    }
+  }
+
+  /**
+   * Implements KernelServiceTerminator::terminate().
+   */
+  public function terminate() {
+    $this->updateCache();
+  }
+
+  /**
+   * Implements CacheCollectorInterface::reset().
+   */
+  public function reset() {
+    $this->storage = array();
+    $this->keysToPersist = array();
+    $this->keysToRemove = array();
+  }
+}
diff --git a/core/lib/Drupal/Core/Cache/CacheCollectorInterface.php b/core/lib/Drupal/Core/Cache/CacheCollectorInterface.php
new file mode 100644
index 0000000..f54f1d7
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheCollectorInterface.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Cache\CacheCollectorInterface.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Provides a caching wrapper to be used in place of large structures.
+ *
+ * This should be extended by systems that need to cache large amounts of data
+ * to calling functions. These structures can become very large, so this
+ * class is used to allow different strategies to be used for caching internally
+ * (lazy loading, building caches over time etc.). This can dramatically reduce
+ * the amount of data that needs to be loaded from cache backends on each
+ * request, and memory usage from static caches of that same data.
+ *
+ * The default implementation is CacheCollector.
+ */
+Interface CacheCollectorInterface {
+
+  /**
+   * Gets value from the cache.
+   *
+   * @param string $key
+   *   Key that identifies the data.
+   *
+   * @return mixed
+   *   The corresponding cache data.
+   */
+  // @todo: This results in a fatal error in PHP <5.3.10.
+  //public function get($key);
+
+  /**
+   * Sets cache data.
+   *
+   * It depends on the specific case and implementation whether this has a
+   * permanent effect or if it just affects the current request.
+   *
+   * @param string $key
+   *   Key that identifies the data.
+   * @param mixed $value
+   *   The data to be set.
+   */
+  public function set($key, $value);
+
+  /**
+   * Deletes the element.
+   *
+   * It depends on the specific case and implementation whether this has a
+   * permanent effect or if it just affects the current request.
+   *
+   * @param string $key
+   *   Key that identifies the data.
+   */
+  public function delete($key);
+
+  /**
+   * Returns whether data exists for this key.
+   *
+   * @param string $key
+   *   Key that identifies the data.
+   */
+  public function has($key);
+
+  /**
+   * Resets the local cache.
+   */
+  public function reset();
+
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 035f282..96ce99d 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -47,6 +47,12 @@ public function build(ContainerBuilder $container) {
       ->addArgument('config');
 
     $container
+      ->register('cache.cache', 'Drupal\Core\Cache\CacheBackendInterface')
+      ->setFactoryClass('Drupal\Core\Cache\CacheFactory')
+      ->setFactoryMethod('get')
+      ->addArgument('cache');
+
+    $container
       ->register('config.storage', 'Drupal\Core\Config\CachedStorage')
       ->addArgument(new Reference('config.cachedstorage.storage'))
       ->addArgument(new Reference('cache.config'));
@@ -83,9 +89,10 @@ public function build(ContainerBuilder $container) {
       ->setFactoryMethod('getSingleton');
 
     // Register the State k/v store as a service.
-    $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueStoreInterface')
-      ->setFactoryService(new Reference('keyvalue'))
-      ->setFactoryMethod('get')
+    $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueCacheDecorator')
+      ->addArgument(new Reference('cache.cache'))
+      ->addArgument(new Reference('lock'))
+      ->addArgument(new Reference('keyvalue'))
       ->addArgument('state');
 
     // Register the Queue factory.
diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php
new file mode 100644
index 0000000..df6e074
--- /dev/null
+++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\KeyValueStore\KeyValueCacheDecorator.
+ */
+
+namespace Drupal\Core\KeyValueStore;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheCollector;
+use Drupal\Core\Lock\LockBackendInterface;
+
+/**
+ * Provides a decorator for a key value store that caches all requested keys.
+ */
+class KeyValueCacheDecorator extends CacheCollector implements KeyValueStoreInterface {
+
+  /**
+   * The key value store to use as state.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+   */
+  protected $keyValueStore;
+
+  public function __construct(CacheBackendInterface $cache, LockBackendInterface $lock, KeyValueFactory $keyValueFactory, $collection) {
+    parent::__construct('state', $cache, $lock);
+    $this->keyValueStore = $keyValueFactory->get($collection);
+  }
+
+  protected function resolveCacheMiss($offset) {
+    $this->storage[$offset] = $this->keyValueStore->get($offset);
+    $this->persist($offset);
+    return $this->storage[$offset];
+  }
+
+  public function delete($key) {
+    parent::delete($key);
+    $this->keyValueStore->delete($key);
+    // Delete the cache to make sure that other requests immediately see the new
+    // value before this request is terminated.
+    $this->cache->delete($this->cid);
+  }
+
+  public function deleteMultiple(array $keys) {
+    foreach ($keys as $key) {
+      $this->delete($key);
+    }
+    $this->keyValueStore->deleteMultiple($keys);
+    // Delete the cache to make sure that other requests immediately see the new
+    // value before this request is terminated.
+    $this->cache->delete($this->cid);
+  }
+
+  public function getAll() {
+    // Don't cache this.
+    return $this->keyValueStore->getAll();
+  }
+
+  public function getCollectionName() {
+    return $this->keyValueStore->getCollectionName();
+  }
+
+  public function getMultiple(array $keys) {
+    $values = array();
+    foreach ($keys as $key) {
+      $values[$key] = $this->get($key);
+    }
+    return $values;
+  }
+
+  public function setIfNotExists($key, $value) {
+    if ($this->keyValueStore->setIfNotExists($key, $value)) {
+      $this->set($key, $value);
+    }
+  }
+
+  public function setMultiple(array $data) {
+    $this->keyValueStore->setMultiple($data);
+    foreach ($data as $key => $value) {
+      parent::set($key, $value);
+    }
+    // Delete the cache to make sure that other requests immediately see the new
+    // value before this request is terminated.
+    $this->cache->delete($this->cid);
+  }
+
+  public function set($key, $value) {
+    $this->keyValueStore->set($key, $value);
+    parent::set($key, $value);
+    // Delete the cache to make sure that other requests immediately see the new
+    // value before this request is terminated.
+    $this->cache->delete($this->cid);
+  }
+
+  /**
+   * @todo: Remove this once the service terminator can be used.
+   */
+  public function __destruct() {
+    $this->terminate();
+  }
+
+}
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index 4531c9f..0788b38 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -143,6 +143,18 @@ public function containerBuild($container) {
       $container
         ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
         ->addArgument(new Reference('service_container'));
+
+      $container
+        ->register('cache.cache', 'Drupal\Core\Cache\CacheBackendInterface')
+        ->setFactoryClass('Drupal\Core\Cache\CacheFactory')
+        ->setFactoryMethod('get')
+        ->addArgument('cache');
+
+      $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueCacheDecorator')
+        ->addArgument(new Reference('cache.cache'))
+        ->addArgument(new Reference('lock'))
+        ->addArgument(new Reference('keyvalue'))
+        ->addArgument('state');
     }
   }
 
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index f886992..ce9ba3e 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -899,6 +899,7 @@ protected function refreshVariables() {
     cache('bootstrap')->delete('variables');
     $conf = variable_initialize();
     drupal_container()->get('config.factory')->reset();
+    drupal_container()->get('state')->reset();
   }
 
   /**
