private void cacheMaintenanceLoop() {
    while (!Thread.interrupted()) {
      long turnStartedAtNs = System.nanoTime();

      try {
        boolean actuallyDidSomeMaintenance = false;

        if (!commandsQueue.isEmpty()) {
          //Closed storages are the easiest way to reclaim pages -- eat that low-hanging fruit first:
          int reclaimed = cleanClosedStoragesAndReclaimPages(/* max per turn: */ 1);

          statistics.closedStoragesReclaimed(reclaimed);
          actuallyDidSomeMaintenance = true;
        }


        //ensure queue is fresh enough -- drop out pages not good enough for reclaim anymore:
        //TODO RC: throttle it each ~5ms? Right now it is useless optimization, because we need
        //         reclaimQueue.size() anyway, and it is basically the same scanning through the queue.
        //         But as soon as we'll have non-O(N) size impl -- this code may be throttled, for sure
        int pagesRemainedForReclaim = 0;
        int pagesDroppedFromReclaimQueue = 0;
        for (Iterator<PageImpl> it = pagesToProbablyReclaim.pagesToProbablyReclaimQueue.iterator();
             it.hasNext(); ) {
          PageImpl page = it.next();
          if (!pagesForReclaimCollector.isGoodForReclaim(page)) {
            it.remove();
            pagesDroppedFromReclaimQueue++;
          }
          else {
            pagesRemainedForReclaim++;
          }
        }

        int pagesDemandForecast = rateController.predictPagesDemandForNextTurn();

        boolean pageDeficitIsLikely = isPageDeficitLikely(pagesRemainedForReclaim, pagesDemandForecast);
        if (pageDeficitIsLikely
            || pagesToProbablyReclaim.isOlderThen(turnStartedAtNs, MILLISECONDS.toNanos(500))) {
          //We collect pages for reclaim based on their recent utility -- but this utility changes
          // with time. Pages good-for-reclaim a second ago -- may become not-so-good for reclaim
          // anymore now. Hence, we need to refresh pagesToReclaim if exhausted -- but also regularly

          refillPagesForReclaim(pagesDemandForecast);

          //if <50% of pages collected to reclaim are clean -> try to eagerly flush some dirty pages:
          //MAYBE RC: flush 50% seems to be an overkill -- better to flush ~pagesToCollect. Since we
          //  collect pages well in advance, i.e. way above forecasted demand in next 1ms, flushing
          //  50% is an overkill, since it is likely most of those pages won't be reclaimed until
          //  next queue refill, or they become dirty again.
          int pagesFlushed = pagesForReclaimCollector.ensureEnoughCleanPagesToReclaim(0.5);

          actuallyDidSomeMaintenance = true;

          pagesRemainedForReclaim = pagesForReclaimCollector.totalPagesPreparedToReclaim();
          pageDeficitIsLikely = isPageDeficitLikely(pagesRemainedForReclaim, pagesDemandForecast);
        }

        if (memoryManager.hasOverflow()) {
          boolean executed = releaseMemoryOverflowThrottler.runThrottled(
            turnStartedAtNs,
            () -> releasePagesAllocatedAboveCapacity(/*maxPagesToRelease: */ 10)
          );

          actuallyDidSomeMaintenance |= executed;
        }


        long timeSpentNs = System.nanoTime() - turnStartedAtNs;

        if (actuallyDidSomeMaintenance) {
          statistics.cacheMaintenanceTurnDone(timeSpentNs);
        }
        else {
          statistics.cacheMaintenanceTurnSkipped(timeSpentNs);
        }


        //Assess allocation pressure and adjust our efforts:
        if (!pageDeficitIsLikely) {
          //allocation pressure low: could collect less and sleep more
          pagesForReclaimCollector.collectLessAggressively();
          synchronized (housekeeperSleepLock) {
            //MAYBE RC: we could predict ~how soon we'll need refill -- based on current reclaimQueue size
            //          and the load forecast -- and increase .wait() time accordingly, e.g. in [1..10]ms
            //          This should reduce CPU consumption in case of low load.
            housekeeperSleepLock.wait(1);
          }
        }
        else if (pagesRemainedForReclaim > 0) {
          //allocation pressure high, but we ~catch up with it: just yield
          Thread.yield();
        }
        else {
          //allocation pressure is so high we can't catch up with it:
          // 1) no time to wait
          // 2) must collect more pages-to-reclaim
          pagesForReclaimCollector.collectMoreAggressively();
        }
      }
      catch (InterruptedException e) {
        break;
      }
      catch (Throwable t) {
        LOG.error("Exception in FilePageCache housekeeper thread (thread continue to run)", t);
      }
    }
    LOG.info("maintenance loop interrupted -> exiting");
  }
private void cacheMaintenanceLoop()