code: @Override
    public void run()
    {
      /**
       * Remember current time as the heartbeat start time. This is used for
       * calculating the delay for the next heartbeat.
       */
      this.lastHeartbeatStartTimeInSecs = System.currentTimeMillis() / 1000;
  
      Set<SFSession> sessionsToHeartbeat = new HashSet<SFSession>();
  
      // synchronously get a copy of the sessions from the global list
      synchronized (this)
      {
        sessionsToHeartbeat.addAll(sessions.keySet());
      }
  
      // heartbeat every session.
      for (SFSession session : sessionsToHeartbeat)
      {
        try
        {
          session.heartbeat();
        }
        catch (Throwable ex)
        {
          LOGGER.error("heartbeat error - message=" + ex.getMessage(), ex);
        }
      }
  
      /**
       * The following is synchronized with the methods to add or remove a
       * session so that we always make sure we have one heartbeat task scheduled
       * when there is any session left.
       */
      synchronized (this)
      {
        // schedule next heartbeat
        if (sessions.size() > 0)
        {
          LOGGER.debug("schedule next heartbeat run");
  
          scheduleHeartbeat();
        }
        else
        {
          LOGGER.debug("no need for heartbeat since no more sessions");
  
          // no need to heartbeat if no more session
          this.heartbeatFuture = null;
        }
      }
    }
docs: Run heartbeat: for each session send a heartbeat request and schedule
  next heartbeat as long as there are sessions left.
  <p>
  Notice that the synchronization is only around the code that visits the
  global sessions map and the code that schedules
  next heartbeat, but not around the heartbeat calls for each session in
  order to minimize the chance of blocking the adding of a session by
  performing the heartbeats. This is because adding a session is called from
  JDBC connection creation call which directly affects application
  performance.