void freeSpace(final String why) {
    if (!isCacheInitialized("BucketCache::freeSpace")) {
      return;
    }
    // Ensure only one freeSpace progress at a time
    if (!freeSpaceLock.tryLock()) {
      return;
    }
    try {
      freeInProgress = true;
      StringBuilder msgBuffer = LOG.isDebugEnabled() ? new StringBuilder() : null;
      long bytesToFreeWithoutExtra = calculateBytesToFree(msgBuffer);
      if (bytesToFreeWithoutExtra <= 0) {
        return;
      }
      long currentSize = bucketAllocator.getUsedSize();
      long totalSize = bucketAllocator.getTotalSize();
      if (LOG.isDebugEnabled() && msgBuffer != null) {
        LOG.debug("Free started because \"" + why + "\"; " + msgBuffer + " of current used="
          + StringUtils.byteDesc(currentSize) + ", actual cacheSize="
          + StringUtils.byteDesc(realCacheSize.sum()) + ", total="
          + StringUtils.byteDesc(totalSize));
      }
      long bytesToFreeWithExtra =
        (long) Math.floor(bytesToFreeWithoutExtra * (1 + extraFreeFactor));
      // Instantiate priority buckets
      BucketEntryGroup bucketSingle =
        new BucketEntryGroup(bytesToFreeWithExtra, blockSize, getPartitionSize(singleFactor));
      BucketEntryGroup bucketMulti =
        new BucketEntryGroup(bytesToFreeWithExtra, blockSize, getPartitionSize(multiFactor));
      BucketEntryGroup bucketMemory =
        new BucketEntryGroup(bytesToFreeWithExtra, blockSize, getPartitionSize(memoryFactor));

      Set<String> allValidFiles = null;
      // We need the region/stores/files tree, in order to figure out if a block is "orphan" or not.
      // See further comments below for more details.
      if (onlineRegions != null) {
        allValidFiles = BlockCacheUtil.listAllFilesNames(onlineRegions);
      }
      // the cached time is recored in nanos, so we need to convert the grace period accordingly
      long orphanGracePeriodNanos = orphanBlockGracePeriod * 1000000;
      long bytesFreed = 0;
      // Scan entire map putting bucket entry into appropriate bucket entry
      // group
      for (Map.Entry<BlockCacheKey, BucketEntry> bucketEntryWithKey : backingMap.entrySet()) {
        BlockCacheKey key = bucketEntryWithKey.getKey();
        BucketEntry entry = bucketEntryWithKey.getValue();
        // Under certain conditions, blocks for regions not on the current region server might
        // be hanging on the cache. For example, when using the persistent cache feature, if the
        // RS crashes, then if not the same regions are assigned back once its online again, blocks
        // for the previous online regions would be recovered and stay in the cache. These would be
        // "orphan" blocks, as the files these blocks belong to are not in any of the online
        // regions.
        // "Orphan" blocks are a pure waste of cache space and should be evicted first during
        // the freespace run.
        // Compactions and Flushes may cache blocks before its files are completely written. In
        // these cases the file won't be found in any of the online regions stores, but the block
        // shouldn't be evicted. To avoid this, we defined this
        // hbase.bucketcache.block.orphan.evictgraceperiod property, to account for a grace
        // period (default 24 hours) where a block should be checked if it's an orphan block.
        if (
          allValidFiles != null
            && entry.getCachedTime() < (System.nanoTime() - orphanGracePeriodNanos)
        ) {
          if (!allValidFiles.contains(key.getHfileName())) {
            if (evictBucketEntryIfNoRpcReferenced(key, entry)) {
              // We calculate the freed bytes, but we don't stop if the goal was reached because
              // these are orphan blocks anyway, so let's leverage this run of freeSpace
              // to get rid of all orphans at once.
              bytesFreed += entry.getLength();
              continue;
            }
          }
        }
        switch (entry.getPriority()) {
          case SINGLE: {
            bucketSingle.add(bucketEntryWithKey);
            break;
          }
          case MULTI: {
            bucketMulti.add(bucketEntryWithKey);
            break;
          }
          case MEMORY: {
            bucketMemory.add(bucketEntryWithKey);
            break;
          }
        }
      }
      PriorityQueue<BucketEntryGroup> bucketQueue =
        new PriorityQueue<>(3, Comparator.comparingLong(BucketEntryGroup::overflow));

      bucketQueue.add(bucketSingle);
      bucketQueue.add(bucketMulti);
      bucketQueue.add(bucketMemory);

      int remainingBuckets = bucketQueue.size();

      BucketEntryGroup bucketGroup;
      while ((bucketGroup = bucketQueue.poll()) != null) {
        long overflow = bucketGroup.overflow();
        if (overflow > 0) {
          long bucketBytesToFree =
            Math.min(overflow, (bytesToFreeWithoutExtra - bytesFreed) / remainingBuckets);
          bytesFreed += bucketGroup.free(bucketBytesToFree);
        }
        remainingBuckets--;
      }

      // Check and free if there are buckets that still need freeing of space
      if (bucketSizesAboveThresholdCount(minFactor) > 0) {
        bucketQueue.clear();
        remainingBuckets = 3;
        bucketQueue.add(bucketSingle);
        bucketQueue.add(bucketMulti);
        bucketQueue.add(bucketMemory);
        while ((bucketGroup = bucketQueue.poll()) != null) {
          long bucketBytesToFree = (bytesToFreeWithExtra - bytesFreed) / remainingBuckets;
          bytesFreed += bucketGroup.free(bucketBytesToFree);
          remainingBuckets--;
        }
      }
      // Even after the above free we might still need freeing because of the
      // De-fragmentation of the buckets (also called Slab Calcification problem), i.e
      // there might be some buckets where the occupancy is very sparse and thus are not
      // yielding the free for the other bucket sizes, the fix for this to evict some
      // of the buckets, we do this by evicting the buckets that are least fulled
      freeEntireBuckets(DEFAULT_FREE_ENTIRE_BLOCK_FACTOR * bucketSizesAboveThresholdCount(1.0f));

      if (LOG.isDebugEnabled()) {
        long single = bucketSingle.totalSize();
        long multi = bucketMulti.totalSize();
        long memory = bucketMemory.totalSize();
        if (LOG.isDebugEnabled()) {
          LOG.debug("Bucket cache free space completed; " + "freed="
            + StringUtils.byteDesc(bytesFreed) + ", " + "total=" + StringUtils.byteDesc(totalSize)
            + ", " + "single=" + StringUtils.byteDesc(single) + ", " + "multi="
            + StringUtils.byteDesc(multi) + ", " + "memory=" + StringUtils.byteDesc(memory));
        }
      }
    } catch (Throwable t) {
      LOG.warn("Failed freeing space", t);
    } finally {
      cacheStats.evict();
      freeInProgress = false;
      freeSpaceLock.unlock();
    }
  }
Free the space if the used size reaches acceptableSize() or one size block couldn't be
allocated. When freeing the space, we use the LRU algorithm and ensure there must be some
blocks evicted
@param why Why we are being called
hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/bucket/BucketCache.java