code: void heartbeatCheck() {
      if (!getNameNode().shouldCheckHeartbeat()) {
        // not to check dead nodes.
        return;
      }
      boolean allAlive = false;
      while (!allAlive) {
        boolean foundDead = false;
        DatanodeID nodeID = null;
  
        // locate the first dead node.
        synchronized (heartbeats) {
          for (Iterator<DatanodeDescriptor> it = heartbeats.iterator();
               it.hasNext();) {
            DatanodeDescriptor nodeInfo = it.next();
            if (isDatanodeDead(nodeInfo)) {
              foundDead = true;
              nodeID = nodeInfo;
              break;
            }
          }
        }
  
        // acquire the fsnamesystem lock, and then remove the dead node.
        if (foundDead) {
          writeLock();
          try {
            synchronized (heartbeats) {
              synchronized (datanodeMap) {
                DatanodeDescriptor nodeInfo = null;
                try {
                  nodeInfo = getDatanode(nodeID);
                } catch (IOException e) {
                  nodeInfo = null;
                }
                if (nodeInfo != null && isDatanodeDead(nodeInfo)) {
                  NameNode.stateChangeLog.info("BLOCK* NameSystem.heartbeatCheck: "
                    + "lost heartbeat from " + nodeInfo.getName());
                  removeDatanode(nodeInfo);
                  nodeInfo.setStartTime(now());
                }
              }
            }
          } finally {
            writeUnlock();
          }
        }
        allAlive = !foundDead;
      }
    }
docs: Check if there are any expired heartbeats, and if so,
  whether any blocks have to be re-replicated.
  While removing dead datanodes, make sure that only one datanode is marked
  dead at a time within the synchronized section. Otherwise, a cascading
  effect causes more datanodes to be declared dead.