docs: private void performCheckpoint(boolean persistentTransactions)
                      throws ObjectManagerException
      {
          final String methodName = "performCheckpoint";
          if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
              trace.entry(this,
                          cclass,
                          methodName,
                          new Boolean(persistentTransactions));
  
          if (gatherStatistics) {
              long now = System.currentTimeMillis();
              waitingBetweenCheckpointsMilliseconds += now - lastCheckpointMilliseconds;
              lastCheckpointMilliseconds = now;
          } // if (gatherStatistics).
  
          // Recover and complete Orphan transactions.
          completeOrphanTransactions();
  
          long logSequenceNumber = 0;
          if (persistentTransactions) {
              // Mark the point in the log that we can truncate to once checkpoint is complete,
              // then write start of checkpoint to the log. This makes sure that the CheckpointStart is recovered first,
              // which ensures that all of the ObjectStores are known at startup.
              CheckpointStartLogRecord checkpointStartLogRecord = new CheckpointStartLogRecord(this);
              // We don't release the reserved space for the Checkpoint log records as
              // we will need to re-reserve it after the checkpoint has completed. Instead we just
              // suppress the check on the log space because we reserved the space for the
              // checkpoint start and end records earlier.
              // Updates to QueueManagerState must be recorded in the log before we
              // flush the ObjectStores, otherwise we risk having named objects in the stores
              // which are not known in the ObjectManager.
              logSequenceNumber = logOutput.markAndWriteNext(checkpointStartLogRecord,
                                                             0,
                                                             false, // Do not check the log space, we reserved it earlier.
                                                             true);
              if (gatherStatistics) {
                  long now = System.currentTimeMillis();
                  flushingCheckpointStartMilliseconds += now - lastCheckpointMilliseconds;
                  lastCheckpointMilliseconds = now;
              } // if (gatherStatistics).
  
              checkpointStarting = CHECKPOINT_STARTING_PERSISTENT;
              // Mark all active transactions as requiring a checkpoint.
              for (java.util.Iterator registeredTransactionIterator = registeredInternalTransactions.values().iterator(); registeredTransactionIterator.hasNext();) {
                  InternalTransaction registeredTransaction = (InternalTransaction) (registeredTransactionIterator.next());
                  // The registered transaction might be null if it was removed from the map after
                  // we built the iterator. See ConcurrentHashMap and java.util.Map.Entry.getValue().
                  if (registeredTransaction != null)
                      registeredTransaction.setRequiresCheckpoint();
              } // for registeredInternalTransactions.
          } else {
              checkpointStarting = CHECKPOINT_STARTING_NONPERSISTENT;
          } // if (persistentTransactions).
  
          // From now on, new transactions write to the following checkpoint in the file store.
          checkpointStarting = CHECKPOINT_STARTING_NO;
  
          // --------------------------------------------------------------------------------------------
          // Pause so that:
          // 1) In flight persistent transactions might complete, avoiding the need to write a checkpoint
          //    record for them.
          // 2) Objects in the checkpoint set which will be written to the Object Store might get deleted
          //    avoiding the need to write them.
          // --------------------------------------------------------------------------------------------
          Object lock = new Object();
          synchronized (lock) {
              try {
                  if (Tracing.isAnyTracingEnabled() && trace.isDebugEnabled())
                      trace.debug(this, cclass, methodName, new Object[] { "wait:867",
                                                                          new Integer(checkpointDelay) });
  
                  lock.wait(checkpointDelay); // Let some transactions complete.
              } catch (InterruptedException exception) {
                  // No FFDC Code Needed.
                  ObjectManager.ffdc.processException(this,
                                                      cclass,
                                                      methodName,
                                                      exception,
                                                      "1:877:1.62");
  
                  if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
                      trace.exit(this,
                                 cclass,
                                 methodName,
                                 exception);
                  throw new UnexpectedExceptionException(this,
                                                         exception);
              } // catch (InterruptedException exception).
          } // synchronized (lock).
  
          if (gatherStatistics) {
              long now = System.currentTimeMillis();
              pausedDuringCheckpointMilliseconds += now - lastCheckpointMilliseconds;
              lastCheckpointMilliseconds = now;
          } // if (gatherStatistics).
  
          // Now that the log is forced tell the active transactions they can safely update the ObjectStores
          // on the assumption that their logRecords prior to logSequenceNumber have been forced to disk.
          // The Transactions take their checkpoint on the assumption that the ObjectStores contain their
          // ManagedObjects. The Object Stores have not been forced yet but the log has,
          // so this is a safe assumption.
          long transactionsRequiringCheckpoint = 0;
          for (java.util.Iterator registeredTransactionIterator = registeredInternalTransactions.values()
                          .iterator(); registeredTransactionIterator.hasNext();) {
              InternalTransaction registeredTransaction = (InternalTransaction) (registeredTransactionIterator.next());
              // The registered transaction might be null if it was removed from the map after
              // we built the iterator. See ConcurrentHashMap and java.util.Map.Entry.getValue().
              // Checkpoint only transactions that have not cleared their reqiuirement for a checkpoint.
  
              if (registeredTransaction != null && registeredTransaction.requiresPersistentCheckpoint) {
                  registeredTransaction.checkpoint(logSequenceNumber);
                  transactionsRequiringCheckpoint++;
              } // if ... registeredTransaction.requiresCheckpoint.
          } // for registeredInternalTransactions.
  
          if (gatherStatistics) {
              totalTransactionsRequiringCheckpoint = totalTransactionsRequiringCheckpoint + transactionsRequiringCheckpoint;
              maximumTransactionsInAnyCheckpoint = Math.max(transactionsRequiringCheckpoint, maximumTransactionsInAnyCheckpoint);
          } // if (gatherStatistics).
  
          // Flush ObjectStores to disk.
          // Loop over a copy of the store collection, in case a new one is registered while we we do this.
          ObjectStore[] stores;
          synchronized (objectStores) {
              stores = (ObjectStore[]) objectStores.values().toArray(new ObjectStore[objectStores.size()]);
          }
          for (int i = 0; i < stores.length; i++) {
              // Let the store decide whether to flush according to its storage strategy.
              if (persistentTransactions)
                  stores[i].flush();
              else if (!stores[i].getPersistence())
                  stores[i].flush();
          } // for.
  
          if (gatherStatistics) {
              long now = System.currentTimeMillis();
              flushingObjectStoresForCheckpointMilliseconds += now - lastCheckpointMilliseconds;
              lastCheckpointMilliseconds = now;
              totalCheckpointsTaken++;
          } // if (gatherStatistics).
  
          if (persistentTransactions) {
              // Write end of checkpoint to the log.
              CheckpointEndLogRecord checkpointEndLogRecord = new CheckpointEndLogRecord();
              logOutput.writeNext(checkpointEndLogRecord,
                                  0,
                                  false, // Do not ckeck the log space, we reserved it earlier.
                                  false);
              // Force the log and truncate to the mark point set when we wrote checkpointStartLogRecord.
              logOutput.truncate();
  
              if (gatherStatistics) {
                  long now = System.currentTimeMillis();
                  flushingEndCheckpointMilliseconds += now - lastCheckpointMilliseconds;
                  lastCheckpointMilliseconds = now;
              } // if (gatherStatistics).
  
              // --------------------------------------------------------------------------------------------
              // If the log is still too full start backing out transactions.
              // --------------------------------------------------------------------------------------------
  
              synchronized (registeredInternalTransactions) {
                  if (logOutput.isOcupancyHigh()) {
                      // Reduce other work in the ObjectManager so that checkpointing can catch up.
                      currentMaximumActiveTransactions = Math.max(registeredInternalTransactions.size() / 2,
                                                                  2);
  
                      // Find the biggest transaction in terms of its reserved space in the log.
                      InternalTransaction biggestTransaction = null;
                      long biggestTransactionSize = 0;
                      for (java.util.Iterator transactionIterator = registeredInternalTransactions.values()
                                      .iterator(); transactionIterator.hasNext();) {
                          InternalTransaction internalTransaction = (InternalTransaction) transactionIterator.next();
                          long transactionSize = internalTransaction.getLogSpaceReserved();
                          if (transactionSize > biggestTransactionSize
                              && internalTransaction.state == Transaction.stateActivePersistent) {
                              biggestTransaction = internalTransaction;
                              biggestTransactionSize = transactionSize;
                          } // if (transactionSize...
                      } // for... registeredInternalTransactions.
  
                      if (biggestTransaction != null) {
                          Transaction transaction = biggestTransaction.getExternalTransaction();
                          // We cannot synchronize on the internal transaction because we might deadlock an application thread.
                          // Application threads may synchronize on their ManagedObjects and then invoke synchronized transaction
                          // methods, so we must not hold a transaction lock when calling backout. For example LinkedLists
                          // synchronize on the list when the preBackout callback is made then they invoke synchronized transaction
                          // methods. If we hold the transaction lock first we would deadlock another thread making transaction
                          // calls while synchronized on the list.
                          // We just catch the state exception and discard it.
                          try {
                              if (biggestTransaction.state == Transaction.stateActivePersistent) {
                                  // It is possible that the biggest transaction has now moved on to some other work,
                                  // but we need to backout something so that will ahve to do.
                                  if (Tracing.isAnyTracingEnabled() && trace.isDebugEnabled())
                                      trace.debug(this, cclass, methodName,
                                                  new Object[] { "Log too full, backing out biggestTransaction:995",
                                                                biggestTransaction });
  
                                  trace.warning(this,
                                                cclass,
                                                methodName,
                                                "ObjectManagerState_LogTooFull",
                                                biggestTransaction);
  
                                  transaction.setTerminationReason(Transaction.terminatedLogTooFull);
                                  transaction.backout(false);
  
                              } // if ( biggestTransaction.state...
                          } catch (InvalidStateException exception) {
                              // No FFDC Code Needed, condition expected when we race with the application thread.
                              if (Tracing.isAnyTracingEnabled() && trace.isEventEnabled())
                                  trace.event(this,
                                              cclass,
                                              methodName,
                                              exception);
                          } // catch (StateErrorException exception).
  
                          requestCheckpoint(true); // Trigger another checkpoint to see if things have got better.
                      }
                  } else { // log file not particularly full.
                      // We had previously reduced the number of active transactions,
                      // this will allw the number to recover. As new transactions come along they
                      // will run and the backlog will be cleared as transactions finish.
                      currentMaximumActiveTransactions = maximumActiveTransactions;
                  } // if (((FileLogOutput)logOutput).getOcupancy()...
  
                  // If we have more transactions than we need release some freeTrasactions.
                  if (totalTransactions > currentMaximumActiveTransactions)
                      totalTransactions = totalTransactions - freeTransactions.clear(totalTransactions - currentMaximumActiveTransactions);
              } // synchronized (registeredInternalTransactions).
          } // if (persistentTransactions).
  
          // finally reset transactions since last checkpoint
          if (persistentTransactions)
              persistentTransactionsSinceLastCheckpoint = 0;
          nonPersistentTransactionsSinceLastCheckpoint = 0;
  
          if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
              trace.exit(this,
                         cclass,
                         methodName);
      }