diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index db68f43cd8..63fa238351 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -503,14 +503,14 @@ function comment_preview(CommentInterface $comment, FormStateInterface $form_sta
     $preview_build['comment_preview'] = $comment_build;
   }
 
+  $build = array();
   if ($comment->hasParentComment()) {
-    $build = [];
     $parent = $comment->getParentComment();
     if ($parent && $parent->isPublished()) {
       $build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($parent);
     }
   }
-  else {
+  elseif ($entity) {
     // The comment field output includes rendering the parent entity of the
     // thread to which the comment is a reply. The rendered entity output
     // includes the comment reply form, which contains the comment preview and
@@ -607,9 +607,8 @@ function template_preprocess_comment(&$variables) {
 
   $variables['submitted'] = t('Submitted by @username on @datetime', ['@username' => $variables['author'], '@datetime' => $variables['created']]);
 
-  if ($comment->hasParentComment()) {
+  if ($comment_parent = $comment->getParentComment()) {
     // Fetch and store the parent comment information for use in templates.
-    $comment_parent = $comment->getParentComment();
     $account_parent = $comment_parent->getOwner();
     $variables['parent_comment'] = $comment_parent;
     $username = [
diff --git a/core/modules/comment/comment.tokens.inc b/core/modules/comment/comment.tokens.inc
index 38529c959d..402ddd1c4b 100644
--- a/core/modules/comment/comment.tokens.inc
+++ b/core/modules/comment/comment.tokens.inc
@@ -200,9 +200,13 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
 
         case 'parent':
           if ($comment->hasParentComment()) {
-            $parent = $comment->getParentComment();
-            $bubbleable_metadata->addCacheableDependency($parent);
-            $replacements[$original] = $parent->getSubject();
+            if ($parent = $comment->getParentComment()) {
+              $bubbleable_metadata->addCacheableDependency($parent);
+              $replacements[$original] = $parent->getSubject();
+            }
+            else {
+              $replacements[$original] = '[not found]';
+            }
           }
           break;
 
@@ -219,10 +223,14 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
           break;
 
         case 'entity':
-          $entity = $comment->getCommentedEntity();
-          $bubbleable_metadata->addCacheableDependency($entity);
-          $title = $entity->label();
-          $replacements[$original] = $title;
+          if ($entity = $comment->getCommentedEntity()) {
+            $bubbleable_metadata->addCacheableDependency($entity);
+            $title = $entity->label();
+            $replacements[$original] = $title;
+          }
+          else {
+            $replacements[$original] = '[not found]';
+          }
           break;
       }
     }
diff --git a/core/modules/comment/src/CommentAccessControlHandler.php b/core/modules/comment/src/CommentAccessControlHandler.php
index 516a291232..1b397aa075 100644
--- a/core/modules/comment/src/CommentAccessControlHandler.php
+++ b/core/modules/comment/src/CommentAccessControlHandler.php
@@ -31,17 +31,19 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
 
     if ($comment_admin) {
       $access = AccessResult::allowed()->cachePerPermissions();
-      return ($operation != 'view') ? $access : $access->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
+      $commented_entity = $entity->getCommentedEntity();
+      return ($operation != 'view' || !$commented_entity) ? $access : $access->andIf($commented_entity->access($operation, $account, TRUE));
     }
 
     switch ($operation) {
       case 'view':
-        $access_result = AccessResult::allowedIf($account->hasPermission('access comments') && $entity->isPublished())->cachePerPermissions()->addCacheableDependency($entity)
-          ->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
+        $access_result = AccessResult::allowedIf($account->hasPermission('access comments') && $entity->isPublished())->cachePerPermissions()->addCacheableDependency($entity);
+        if ($commented_entity = $entity->getCommentedEntity()) {
+          $access_result = $access_result->andIf($entity->getCommentedEntity()->access($operation, $account, TRUE));
+        }
         if (!$access_result->isAllowed()) {
           $access_result->setReason("The 'access comments' permission is required and the comment must be published.");
         }
-
         return $access_result;
 
       case 'update':
@@ -124,7 +126,7 @@ protected function checkFieldAccess($operation, FieldDefinitionInterface $field_
         /** @var \Drupal\comment\CommentInterface $entity */
         $entity = $items->getEntity();
         $commented_entity = $entity->getCommentedEntity();
-        $anonymous_contact = $commented_entity->get($entity->getFieldName())->getFieldDefinition()->getSetting('anonymous');
+        $anonymous_contact = $commented_entity ? $commented_entity->get($entity->getFieldName())->getFieldDefinition()->getSetting('anonymous') : COMMENT_ANONYMOUS_MAY_CONTACT;
         $admin_access = AccessResult::allowedIfHasPermission($account, 'administer comments');
         $anonymous_access = AccessResult::allowedIf($entity->isNew() && $account->isAnonymous() && ($anonymous_contact != CommentInterface::ANONYMOUS_MAYNOT_CONTACT || $is_name) && $account->hasPermission('post comments'))
           ->cachePerPermissions()
diff --git a/core/modules/comment/src/CommentInterface.php b/core/modules/comment/src/CommentInterface.php
index 6cc3685099..be17e5d0a1 100644
--- a/core/modules/comment/src/CommentInterface.php
+++ b/core/modules/comment/src/CommentInterface.php
@@ -40,6 +40,10 @@ interface CommentInterface extends ContentEntityInterface, EntityChangedInterfac
   /**
    * Determines if this comment is a reply to another comment.
    *
+   * Calling getParentComment() without checking hasParentComment() first, is
+   * allowed. hasParentComment() is generally faster but can still return a
+   * value even when the parent comment cannot be loaded.
+   *
    * @return bool
    *   TRUE if the comment has a parent comment otherwise FALSE.
    */
@@ -49,7 +53,8 @@ public function hasParentComment();
    * Returns the parent comment entity if this is a reply to a comment.
    *
    * @return \Drupal\comment\CommentInterface|null
-   *   A comment entity of the parent comment or NULL if there is no parent.
+   *   A comment entity of the parent comment; NULL if there is no parent or
+   *   if the parent somehow cannot be loaded.
    */
   public function getParentComment();
 
@@ -65,6 +70,10 @@ public function getCommentedEntity();
   /**
    * Returns the ID of the entity to which the comment is attached.
    *
+   * Calling getCommentedEntity() without checking getCommentedEntityId() first,
+   * is allowed. getCommentedEntityId() is generally faster but can still return
+   * a value even when the commented entity cannot be loaded.
+   *
    * @return int
    *   The ID of the entity to which the comment is attached.
    */
diff --git a/core/modules/comment/src/CommentLazyBuilders.php b/core/modules/comment/src/CommentLazyBuilders.php
index 90c2c696fc..bfe0f034c1 100644
--- a/core/modules/comment/src/CommentLazyBuilders.php
+++ b/core/modules/comment/src/CommentLazyBuilders.php
@@ -139,7 +139,7 @@ public function renderLinks($comment_entity_id, $view_mode, $langcode, $is_in_pr
       $entity = $this->entityTypeManager->getStorage('comment')->load($comment_entity_id);
       $commented_entity = $entity->getCommentedEntity();
 
-      $links['comment'] = $this->buildLinks($entity, $commented_entity);
+      $links['comment'] = $commented_entity ? $this->buildLinks($entity, $commented_entity) : array();
 
       // Allow other modules to alter the comment links.
       $hook_context = [
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index b3fd17e59c..32bc3382b2 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -80,9 +80,11 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
 
     /** @var \Drupal\comment\CommentInterface $entity */
     // Store a threading field setting to use later in self::buildComponents().
-    $build['#comment_threaded'] = $entity->getCommentedEntity()
-      ->getFieldDefinition($entity->getFieldName())
-      ->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED;
+    $commented_entity = $entity->getCommentedEntity();
+    $build['#comment_threaded'] =
+      !$commented_entity
+      || $commented_entity->getFieldDefinition($entity->getFieldName())
+           ->getSetting('default_mode') === CommentManagerInterface::COMMENT_MODE_THREADED;
     // If threading is enabled, don't render cache individual comments, but do
     // keep the cacheability metadata, so it can bubble up.
     if ($build['#comment_threaded']) {
@@ -142,7 +144,8 @@ public function buildComponents(array &$build, array $entities, array $displays,
       $commented_entity = $entity->getCommentedEntity();
 
       $build[$id]['#entity'] = $entity;
-      $build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__' . $commented_entity->bundle();
+      $build[$id]['#theme'] = 'comment__' . $entity->getFieldName() . '__'
+                              . ($commented_entity ? $commented_entity->bundle() : '');
 
       $display = $displays[$entity->bundle()];
       if ($display->getComponent('links')) {
@@ -164,7 +167,9 @@ public function buildComponents(array &$build, array $entities, array $displays,
         $build[$id]['#attached'] = [];
       }
       $build[$id]['#attached']['library'][] = 'comment/drupal.comment-by-viewer';
-      if ($attach_history && $commented_entity->getEntityTypeId() === 'node') {
+      if ($attach_history
+        && $commented_entity->getEntityTypeId() === 'node'
+        && $commented_entity) {
         $build[$id]['#attached']['library'][] = 'comment/drupal.comment-new-indicator';
 
         // Embed the metadata for the comment "new" indicators on this node.
diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php
index d7230bb186..b14e2e8ec1 100644
--- a/core/modules/comment/src/Entity/Comment.php
+++ b/core/modules/comment/src/Entity/Comment.php
@@ -203,8 +203,8 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
    */
   public function referencedEntities() {
     $referenced_entities = parent::referencedEntities();
-    if ($this->getCommentedEntityId()) {
-      $referenced_entities[] = $this->getCommentedEntity();
+    if ($commented_entity = $this->getCommentedEntity()) {
+      $referenced_entities[] = $commented_entity;
     }
     return $referenced_entities;
   }
@@ -404,8 +404,8 @@ public function setSubject($subject) {
    * {@inheritdoc}
    */
   public function getAuthorName() {
-    if ($this->get('uid')->target_id) {
-      return $this->get('uid')->entity->label();
+    if ($user = $this->get('uid')->entity) {
+      return $user->label();
     }
     return $this->get('name')->value ?: \Drupal::config('user.settings')->get('anonymous');
   }
@@ -424,8 +424,9 @@ public function setAuthorName($name) {
   public function getAuthorEmail() {
     $mail = $this->get('mail')->value;
 
-    if ($this->get('uid')->target_id != 0) {
-      $mail = $this->get('uid')->entity->getEmail();
+    $user = $this->get('uid')->entity;
+    if ($user && $user->id() != 0) {
+      $mail = $user->getEmail();
     }
 
     return $mail;
diff --git a/core/modules/comment/tests/modules/comment_test/src/CommentTestSqlEntityStorage.php b/core/modules/comment/tests/modules/comment_test/src/CommentTestSqlEntityStorage.php
new file mode 100644
index 0000000000..7d61672fb9
--- /dev/null
+++ b/core/modules/comment/tests/modules/comment_test/src/CommentTestSqlEntityStorage.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\comment_test\CommentTestSqlEntityStorage.
+ */
+
+namespace Drupal\comment_test;
+
+use Drupal\comment\CommentStorage;
+
+/**
+ * Comment storage handler for the comment_test.module.
+ */
+class CommentTestSqlEntityStorage extends CommentStorage {
+
+  /**
+   * Intentionally makes reference data (commented entity, parent comment,
+   * author) invalid, in the SQL storage backend.
+   *
+   * @param array $comment_ids
+   *   IDs of comments to orphan. If not provided, one comment will be taken
+   *   semi randomly (if one exists).
+   * @param array $fields
+   *   Names of fields to change. Allowed values: 'pid', 'entity_id', 'uid'. If
+   *   not provided, all fields will be changed.
+   *
+   * @return array
+   *   The orphaned comment ID(s), i.e. the input argument if it was nonempty.
+   */
+  public function orphanComments(array $comment_ids = array(), array $fields = array()) {
+
+    // Get the maximum parent/author and set it one higher; this emulates
+    // a parent/author which has been deleted after creating.
+    $query = $this->database->select('comment_field_data', 'c');
+    $query->addExpression('MAX(cid)', 'cid_max');
+    $query->addExpression('MAX(pid)', 'pid_max');
+    $query->addExpression('MAX(uid)', 'uid_max');
+    $query->addExpression('MAX(entity_id)', 'entity_id_max');
+    $row = $query->execute()->fetchObject();
+    if (!isset($row->pid_max) && isset($row->cid_max)) {
+      $row->pid_max = $row->cid_max;
+    }
+
+    if (empty($comment_ids) && isset($row->cid_max)) {
+      $comment_ids = array($row->cid_max);
+    }
+
+    if (!empty($comment_ids)) {
+      $update_values = array('entity_id' => $row->entity_id_max + 1, 'pid' => $row->pid_max + 1, 'uid' => $row->uid_max + 1);
+      if ($fields) {
+        $update_values = array_intersect_key($update_values, array_flip($fields));
+      }
+      $this->database->update('comment_field_data')
+        ->fields($update_values)
+        ->condition('cid', $comment_ids)
+        ->execute();
+
+      $this->resetCache();
+    }
+  }
+
+}
diff --git a/core/modules/comment/tests/src/Functional/CommentOrphanedTest.php b/core/modules/comment/tests/src/Functional/CommentOrphanedTest.php
new file mode 100644
index 0000000000..920703d2e4
--- /dev/null
+++ b/core/modules/comment/tests/src/Functional/CommentOrphanedTest.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Drupal\Tests\comment\Kernel;
+
+use Drupal\Tests\comment\Functional\CommentTestBase;
+use Drupal\Tests\EntityViewTrait;
+
+/**
+ * Tests operations on orphaned comments.
+ *
+ * Drupal core will officially never orphan comments (i.e. officially, comments
+ * cannot end up with nonexistent parent comments or commented entities). This
+ * is why the test is pretty contrived.
+ *
+ * This test exists despite the above because it's expected that in time someone
+ * will try to be smart and delete old entities/comments from a large database,
+ * a PHP process will die at exactly the wrong time, or whatever exotic event
+ * happens that makes comments with invalid parent/entity references exist. When
+ * this happens, a site should not start crashing during basic operations like
+ * loading/displaying a node page. Practice has shown that it's fairly easy to
+ * inadvertently write code that makes Drupal crash during loading/rendering
+ * orphaned comments; this test will draw attention to that code in Drupal core.
+ *
+ * @group comment
+ */
+class CommentOrphanedTest extends CommentTestBase {
+
+  use EntityViewTrait {
+    buildEntityView as drupalBuildEntityView;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'classy';
+
+  /**
+   * Modules to install. We would like to have every module installed that
+   * implements hook_comment_storage_load(), so we can implicitly test it.
+   * At this moment, RDF is the only core module implementing the hook.
+   *
+   * @var array
+   */
+  public static $modules = ['comment_test', 'rdf', 'comment', 'node'];
+
+  /**
+   * Test loading/deleting/rendering orphaned comments.
+   */
+  public function testDeleteOrphanedComment() {
+    /** @var \Drupal\Core\Entity\EntityTypeManager $manager */
+    $manager = \Drupal::service('entity_type.manager');
+
+    //  We only know how to orphan comments on SQL based entity storage.
+    $storage = $manager->getStorage('comment');
+    if (in_array('Drupal\Core\Entity\Sql\SqlEntityStorageInterface', class_implements($storage))) {
+
+      // Make sure we have a comment.
+      $this->drupalLogin($this->webUser);
+      $comment_text = $this->randomMachineName();
+      $subject = $this->randomMachineName();
+      $this->postComment($this->node, $comment_text, $subject);
+
+      // Mess with the comment to make it orphaned.
+      $original_class = get_class($storage);
+      $manager->clearCachedDefinitions();
+      $manager->getDefinition('comment')
+        ->setStorageClass('Drupal\comment_test\CommentTestSqlEntityStorage');
+
+      $storage = $manager->getStorage('comment');
+      // Invalidate the author/parent comment, not the commented entity.
+      $storage->orphanComments([], ['pid', 'uid']);
+
+      // Render the entity, with orphaned comment, implicitly testing that
+      // nothing breaks. (setUp() configured the field so that in 'full' i.e.
+      // default view mode, comments are rendered.)
+      $built = $this->drupalBuildEntityView($this->node);
+      \Drupal::service('renderer')->renderPlain($built);
+
+      $ids = $storage->orphanComments();
+
+      // Load, render (standalone without commented entity) and delete comments,
+      // implicitly testing that nothing breaks when the commented entity is not
+      // available. This does not have a current practical application (there
+      // are no standalone 'comment pages') but it ensures some decoupling of
+      // the comment view/render code, which is a good thing in itself.
+      $entities = $storage->loadMultiple($ids);
+      $built = $this->drupalBuildEntityView(reset($entities), 'full', NULL, TRUE);
+      \Drupal::service('renderer')->renderPlain($built);
+      $storage->delete($entities);
+
+      $manager->clearCachedDefinitions();
+      $manager->getDefinition('comment')->setStorageClass($original_class);
+    }
+
+  }
+
+}
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index 92cc337fc1..cbd696e05d 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -237,12 +237,13 @@ function rdf_comment_storage_load($comments) {
       ->getPreparedFieldMapping('created');
     /** @var \Drupal\comment\CommentInterface $comment*/
     $comment->rdf_data['date'] = rdf_rdfa_attributes($created_mapping, $comment->get('created')->first()->toArray());
-    $entity = $comment->getCommentedEntity();
-    // The current function is a storage level hook, so avoid to bubble
-    // bubbleable metadata, because it can be outside of a render context.
-    $comment->rdf_data['entity_uri'] = $entity->toUrl()->toString(TRUE)->getGeneratedUrl();
-    if ($comment->hasParentComment()) {
-      $comment->rdf_data['pid_uri'] = $comment->getParentComment()->toUrl()->toString(TRUE)->getGeneratedUrl();
+    if ($entity = $comment->getCommentedEntity()) {
+      // The current function is a storage level hook, so avoid to bubble
+      // bubbleable metadata, because it can be outside of a render context.
+      $comment->rdf_data['entity_uri'] = $entity->toUrl()->toString(TRUE)->getGeneratedUrl();
+    }
+    if ($parent = $comment->getParentComment()) {
+      $comment->rdf_data['pid_uri'] = $parent->toUrl()->toString(TRUE)->getGeneratedUrl();
     }
   }
 }
