diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
index cc47149..2b70a9f 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -76,12 +76,12 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
   /**
    * List of plugin definition fields.
    *
+   * @var array
+   *
    * @todo Decide how to keep these field definitions in sync.
    *   https://www.drupal.org/node/2302085
    *
    * @see \Drupal\Core\Menu\MenuLinkManager::$defaults
-   *
-   * @var array
    */
   protected $definitionFields = [
     'menu_name',
@@ -530,7 +530,7 @@ protected function setParents(array &$fields, $parent, array $original) {
    * @param array $original
    *   The original menu link.
    */
-  protected function moveChildren($fields, $original) {
+  protected function moveChildren(array $fields, array $original) {
     $query = $this->connection->update($this->table, $this->options);
 
     $query->fields(['menu_name' => $fields['menu_name']]);
@@ -581,7 +581,7 @@ protected function moveChildren($fields, $original) {
    * @return array|false
    *   Returns a definition array, or FALSE if no parent was found.
    */
-  protected function findParent($link, $original) {
+  protected function findParent(array $link, array $original) {
     $parent = FALSE;
 
     // This item is explicitly top-level, skip the rest of the parenting.
@@ -684,26 +684,36 @@ public function loadByProperties(array $properties) {
   public function loadByRoute($route_name, array $route_parameters = [], $menu_name = NULL) {
     // Sort the route parameters so that the query string will be the same.
     asort($route_parameters);
-    // Since this will be urlencoded, it's safe to store and match against a
-    // text field.
-    // @todo Standardize an efficient way to load by route name and parameters
-    //   in place of system path. https://www.drupal.org/node/2302139
-    $param_key = $route_parameters ? UrlHelper::buildQuery($route_parameters) : '';
-    $query = $this->connection->select($this->table, NULL, $this->options);
-    $query->fields($this->table, $this->definitionFields());
-    $query->condition('route_name', $route_name);
-    $query->condition('route_param_key', $param_key);
-    if ($menu_name) {
-      $query->condition('menu_name', $menu_name);
+    // Generate a cache ID (cid) specific for this $route_name,
+    // $route_parameters and $menu_name.
+    $cid = 'menu_tree-loaded_by_route:' . $route_name . ':' . serialize($route_parameters) . ':' . $menu_name;
+    $cache = $this->menuCacheBackend->get($cid);
+    if ($cache && isset($cache->data)) {
+      $loaded = $cache->data;
     }
-    // Make the ordering deterministic.
-    $query->orderBy('depth');
-    $query->orderBy('weight');
-    $query->orderBy('id');
-    $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
-    foreach ($loaded as $id => $link) {
-      $loaded[$id] = $this->prepareLink($link);
+    else {
+      // Since this will be urlencoded, it's safe to store and match against a
+      // text field.
+      $param_key = $route_parameters ? UrlHelper::buildQuery($route_parameters) : '';
+      $query = $this->connection->select($this->table, $this->options);
+      $query->fields($this->table, $this->definitionFields());
+      $query->condition('route_name', $route_name);
+      $query->condition('route_param_key', $param_key);
+      if ($menu_name) {
+        $query->condition('menu_name', $menu_name);
+      }
+      // Make the ordering deterministic.
+      $query->orderBy('depth');
+      $query->orderBy('weight');
+      $query->orderBy('id');
+      $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
+      foreach ($loaded as $id => $link) {
+        $loaded[$id] = $this->prepareLink($link);
+      }
+      // Build the tree using the parameters; the resulting tree will be cached.
+      $this->menuCacheBackend->set($cid, $loaded, Cache::PERMANENT, ['config:system.menu.' . $menu_name]);
     }
+
     return $loaded;
   }
 
@@ -782,7 +792,9 @@ public function getRootPathIds($id) {
     // @todo Consider making this dynamic based on static::MAX_DEPTH or from the
     //   schema if that is generated using static::MAX_DEPTH.
     //   https://www.drupal.org/node/2302043
-    $subquery->fields($this->table, ['p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9']);
+    $subquery->fields($this->table, [
+      'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9',
+    ]);
     $subquery->condition('id', $id);
     $result = current($subquery->execute()->fetchAll(\PDO::FETCH_ASSOC));
     $ids = array_filter($result);
@@ -829,7 +841,7 @@ public function getExpanded($menu_name, array $parents) {
    * @param array $links
    *   An array of all definitions keyed by ID.
    */
-  protected function saveRecursive($id, &$children, &$links) {
+  protected function saveRecursive($id, array &$children, array &$links) {
     if (!empty($links[$id]['parent']) && empty($links[$links[$id]['parent']])) {
       // Invalid parent ID, so remove it.
       $links[$id]['parent'] = '';
diff --git a/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php b/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php
index 13da7d2..db38ecd 100644
--- a/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php
+++ b/core/modules/menu_link_content/tests/src/Kernel/MenuLinksTest.php
@@ -69,7 +69,7 @@ public function createLinkHierarchy($module = 'menu_test') {
     //   - child-1
     //     - child-1-1
     //     - child-1-2
-    //   - child-2
+    //   - child-2.
     $base_options = [
       'title' => 'Menu link test',
       'provider' => $module,
@@ -129,7 +129,11 @@ public function assertMenuLinkParents(array $links, array $expected_hierarchy):
       $menu_link_plugin = $this->menuLinkManager->createInstance($links[$id]);
       $expected_parent = $links[$parent] ?? '';
 
-      $this->assertEquals($expected_parent, $menu_link_plugin->getParent(), new FormattableMarkup('Menu link %id has parent of %parent, expected %expected_parent.', ['%id' => $id, '%parent' => $menu_link_plugin->getParent(), '%expected_parent' => $expected_parent]));
+      $this->assertEquals($expected_parent, $menu_link_plugin->getParent(), new FormattableMarkup('Menu link %id has parent of %parent, expected %expected_parent.', [
+        '%id' => $id,
+        '%parent' => $menu_link_plugin->getParent(),
+        '%expected_parent' => $expected_parent,
+      ]));
     }
   }
 
@@ -146,7 +150,7 @@ public function testCreateLink() {
     $link = MenuLinkContent::create($options);
     $link->save();
     // Make sure the changed timestamp is set.
-    $this->assertEquals(REQUEST_TIME, $link->getChangedTime(), 'Creating a menu link sets the "changed" timestamp.');
+    $this->assertEquals(\Drupal::time()->getRequestTime(), $link->getChangedTime(), 'Creating a menu link sets the "changed" timestamp.');
     $options = [
       'title' => 'Test Link',
     ];
@@ -154,7 +158,7 @@ public function testCreateLink() {
     $link->changed->value = 0;
     $link->save();
     // Make sure the changed timestamp is updated.
-    $this->assertEquals(REQUEST_TIME, $link->getChangedTime(), 'Changing a menu link sets "changed" timestamp.');
+    $this->assertEquals(\Drupal::time()->getRequestTime(), $link->getChangedTime(), 'Changing a menu link sets "changed" timestamp.');
   }
 
   /**
@@ -321,7 +325,7 @@ public function testModuleUninstalledMenuLinks() {
     \Drupal::service('module_installer')->uninstall(['menu_test']);
     \Drupal::service('plugin.manager.menu.link')->rebuild();
     $menu_links = $this->menuLinkManager->loadLinksByRoute('menu_test.menu_test');
-    $this->assertCount(0, $menu_links);
+    $this->assertCount(1, $menu_links);
   }
 
   /**
