public long markBuffersForProactiveEviction(Predicate<CacheTag> predicate, boolean isInstantDeallocation) {
    long markedBytes = 0;
    // Proactive eviction does not need to be perfectly accurate - the iterator returned here might be missing some
    // concurrent inserts / removals but it's fine for us here.
    Collection<FileCache<FileData>> fileCaches = cache.values();
    for (FileCache<FileData> fileCache : fileCaches) {
      if (predicate.test(fileCache.getTag()) && fileCache.incRef()) {
        // Locked on subcache so that the async cleaner won't pull out buffers in an unlikely scenario.
        try {
          ArrayList<StripeData> stripeDataList = fileCache.getCache().getData();
          for (StripeData stripeData : stripeDataList) {
            for (int i = 0; i < stripeData.getData().length; ++i) {
              LlapSerDeDataBuffer[][] colData = stripeData.getData()[i];
              if (colData == null) continue;
              for (int j = 0; j < colData.length; ++j) {
                LlapSerDeDataBuffer[] streamData = colData[j];
                if (streamData == null) continue;
                for (int k = 0; k < streamData.length; ++k) {
                  markedBytes += streamData[k].markForEviction();
                  if (isInstantDeallocation) {
                    allocator.deallocate(streamData[k]);
                  }
                }
              }
            }
          }
        } finally {
          fileCache.decRef();
        }
      }
    }
    return markedBytes;
  }
RW lock ensures we have a consistent view of the file data, which is important given that
we generate "stripe" boundaries arbitrarily. Reading buffer data itself doesn't require
that this lock is held; however, everything else in stripes list does.
TODO: make more granular? We only care that each one reader sees consistent boundaries.
      So, we could shallow-copy the stripes list, then have individual locks inside each.
llap-server/src/java/org/apache/hadoop/hive/llap/cache/SerDeLowLevelCacheImpl.java