code: @Override
    public void close() {
      if (isDefaultManager() && getClass().getClassLoader() == classLoader) {
        log.info("Closing default CacheManager");
      }
      Iterable<Cache> _caches;
      synchronized (lock) {
        if (closing) {
          return;
        }
        _caches = cachesCopy();
        closing = true;
      }
      logPhase("close");
      List<Throwable> _suppressedExceptions = new ArrayList<Throwable>();
      for (Cache c : _caches) {
        ((InternalCache) c).cancelTimerJobs();
      }
      for (Cache c : _caches) {
        try {
          c.close();
        } catch (Throwable t) {
          _suppressedExceptions.add(t);
        }
      }
      try {
        for (CacheManagerLifeCycleListener lc : cacheManagerLifeCycleListeners) {
          lc.managerDestroyed(this);
        }
      } catch (Throwable t) {
        _suppressedExceptions.add(t);
      }
      ((Cache2kCoreProviderImpl) PROVIDER).removeManager(this);
      synchronized (lock) {
        for (Cache c : cacheNames.values()) {
          log.warn("unable to close cache: " + c.getName());
        }
      }
      eventuallyThrowException(_suppressedExceptions);
      cacheNames = null;
    }
docs: The shutdown takes place in two phases. First all caches are notified to
  cancel their scheduled timer jobs, after that the shutdown is done. Cancelling
  the timer jobs first is needed, because there may be cache stacking and
  a timer job of one cache may call an already closed cache.
  
  <p>Rationale exception handling: Exceptions on shutdown just could silently
  ignored, because a shutdown is done any way. Exceptions could be happened
  long before in a parallel task, shutdown is the last point where this could
  be propagated to the application. Silently ignoring is bad anyway, because
  this will cause that serious problems keep undetected. The exception and
  error handling within the cache tries everything that exceptions will be
  routed through as early and directly as possible.