void doCleanup() {
    RuntimeException exception = null;
    try {
      closed = true;
      seenEvents.clear();
      seenResults.clear();
      freePendingCallbacks();
      if (locks != null) {
        final long conflictStart = statisticsClock.getTime();
        try {
          locks.cleanup(getCache().getInternalDistributedSystem());
        } catch (IllegalArgumentException | IllegalMonitorStateException e) {
          exception = e;
        }
        if (statisticsClock.isEnabled()) {
          proxy.getTxMgr().getCachePerfStats()
              .incTxConflictCheckTime(statisticsClock.getTime() - conflictStart);
        }
      }
      for (final Map.Entry<InternalRegion, TXRegionState> me : regions.entrySet()) {
        InternalRegion r = me.getKey();
        TXRegionState txrs = me.getValue();
        /*
         * Need to unlock the primary lock for rebalancing so that rebalancing can resume.
         */
        if (gotBucketLocks) {
          if (r instanceof BucketRegion && (((BucketRegion) r).getBucketAdvisor().isPrimary())) {
            try {
              ((BucketRegion) r).doUnlockForPrimary();
            } catch (RegionDestroyedException rde) {
              // ignore
              if (logger.isDebugEnabled()) {
                logger.debug("RegionDestroyedException while unlocking bucket region {}",
                    r.getFullPath(), rde);
              }
            } catch (Exception rde) {
              // ignore
              if (logger.isDebugEnabled()) {
                logger.debug(
                    "Exception while unlocking bucket region {} this is probably because the bucket was destroyed and never locked initially.",
                    r.getFullPath(), rde);
              }
            }
          }
        }
        txrs.cleanup(r);
      }
    } finally {
      synchronized (completionGuard) {
        completionGuard.notifyAll();
      }

      if (exception != null && !proxy.getCache().isClosed()) {
        throw exception;
      }
    }
  }
Note that cleanup does more than is needed in this method. This method only needs to do stuff
that is required when a Cache close is done and we have txs that are still in progress.
Currently the only thing that is needed is to decrement off-heap refcounts since off-heap
memory lives after a cache close.
geode-core/src/main/java/org/apache/geode/internal/cache/TXState.java