@Override
  public void allocateMultiple(MemoryBuffer[] dest, int size, BufferObjectFactory factory, AtomicBoolean isStopped)
      throws AllocatorOutOfMemoryException {
    checkAllocationSize(size);
    int freeListIx = determineFreeListForAllocation(size);
    int allocLog2 = freeListIx + minAllocLog2;
    int allocationSize = 1 << allocLog2;

    // If using async, we could also reserve one by one.
    memoryManager.reserveMemory(dest.length << allocLog2, isStopped);
    initMemoryBuffers(dest, factory);
    int arenaCount = getArenaCount();

    // Note: we might want to be smarter if threadId-s are low and there more arenas than threads.
    long threadId = arenaCount > 1 ? Thread.currentThread().getId() : 0;
    int destAllocIx = allocateFast(dest, null, 0, dest.length,
        freeListIx, allocationSize, (int)(threadId % arenaCount), arenaCount);
    if (destAllocIx == dest.length) {
      return;
    }
    // Try to allocate memory if we haven't allocated all the way to maxSize yet; very rare.
    destAllocIx = allocateWithExpand(dest, destAllocIx, freeListIx, allocationSize, arenaCount);
    if (destAllocIx == dest.length) {
      return;
    }

    // Another thread might have allocated a new arena
    arenaCount = getArenaCount();

    // We called reserveMemory so we know that there's memory waiting for us somewhere.
    // But that can mean that the reserved memory is fragmented thus unusable
    // or we have a common class of rare race conditions related to the order of locking/checking of
    // different allocation areas this is due to the fact that Allocations arrive in a burst fashion.
    // Simple case - say we have 2 arenas, 256Kb available in arena 2.
    // We look at arena 1; someone deallocs 256Kb from arena 1 and allocs the same from arena 2;
    // we look at arena 2 and find no memory. Or, for single arena, 2 threads reserve 256k each,
    // and a single 1Mb block is available. When the 1st thread locks the 1Mb freelist, the 2nd one
    // might have already examined the 256k and 512k lists, finding nothing. Blocks placed by (1)
    // into smaller lists after its split is done will not be found by (2); given that freelist
    // locks don't overlap, (2) may even run completely between the time (1) takes out the 1Mb
    // block and the time it returns the remaining 768Kb.
    // To mitigate that as of now:
    //  - First we try again (fast path):
    //  - Second try to push on the memoryManger/evictor to evict more.
    //  - Third bypass the memory manager and use discards method to brute force evict if needed.
    int allocationAttempt = 0;
    long totalForceEvictedBytes = 0;
    long currentEvicted;
    int startArenaIx;
    int emptyAttempt = 0;
    while (totalForceEvictedBytes < maxForcedEvictionSize && emptyAttempt < MAX_DISCARD_ATTEMPTS) {
      startArenaIx = (int) ((threadId + allocationAttempt) % arenaCount);
      if (allocationAttempt >= MAX_FAST_ATTEMPT) {
        // Try to evict more starting from dest.length - destAllocIx << allocLog2, exponentially increase the eviction
        // request based on forceEvictAttempt counter. Why Exponentially increase, usually query fragments triggers a
        // burst of allocation at the same time, IMO it is worth to over evict thus next allocation will get free lunch.
        currentEvicted =
            memoryManager.evictMemory(dest.length - destAllocIx + allocationAttempt - MAX_FAST_ATTEMPT << allocLog2);
        // Note that memoryManager.evictMemory might evict 0 thus bail out after MAX_DISCARD_ATTEMPTS
        totalForceEvictedBytes += currentEvicted;
        emptyAttempt += currentEvicted == 0 ? 1 : 0;
      }
      destAllocIx =
          allocateFast(dest, null, destAllocIx, dest.length, freeListIx, allocationSize, startArenaIx, arenaCount);
      if (destAllocIx == dest.length) {
        return;
      }
      destAllocIx =
          allocateWithSplit(dest,
              null,
              destAllocIx,
              dest.length,
              freeListIx,
              allocationSize,
              startArenaIx,
              arenaCount,
              -1);
      if (destAllocIx == dest.length) {
        return;
      }

      allocationAttempt++;
    }

    LlapIoImpl.LOG.warn(FAILED_TO_ALLOCATE_MSG,
        dest.length,
        allocationSize,
        allocationAttempt,
        totalForceEvictedBytes,
        destAllocIx << allocLog2);
    int discardsAttempt = 0;
    boolean isFailed = false;
    int memoryForceReleased = 0;
    try {
      int discardFailed = 0;
      while (true) {
        // Try to split bigger blocks.
        startArenaIx = (int)((threadId + discardsAttempt + allocationAttempt) % arenaCount);
        destAllocIx = allocateWithSplit(dest, null, destAllocIx, dest.length,
            freeListIx, allocationSize, startArenaIx, arenaCount, -1);
        if (destAllocIx == dest.length) return;
        // Try to force-evict the fragments of the requisite size.
        boolean hasDiscardedAny = false;
        DiscardContext ctx = threadCtx.get();
        // Brute force may discard up to twice as many buffers.
        int maxListSize = 1 << (doUseBruteDiscard ? freeListIx : (freeListIx - 1));
        int requiredBlocks = dest.length - destAllocIx;
        ctx.init(maxListSize, requiredBlocks);
        // First, try to use the blocks of half size in every arena.
        if (doUseFreeListDiscard && freeListIx > 0) {
          discardBlocksBasedOnFreeLists(freeListIx, startArenaIx, arenaCount, ctx);
          memoryForceReleased += ctx.memoryReleased;
          hasDiscardedAny = ctx.resultCount > 0;
          destAllocIx = allocateFromDiscardResult(
              dest, destAllocIx, freeListIx, allocationSize, ctx);
          if (destAllocIx == dest.length) {
            return;
          }
        }
        // Then, try the brute force search for something to throw away.
        if (doUseBruteDiscard) {
          ctx.resetResults();
          discardBlocksBruteForce(freeListIx, startArenaIx, arenaCount, ctx);
          memoryForceReleased += ctx.memoryReleased;
          hasDiscardedAny = hasDiscardedAny || (ctx.resultCount > 0);
          destAllocIx = allocateFromDiscardResult(
              dest, destAllocIx, freeListIx, allocationSize, ctx);
          if (destAllocIx == dest.length) {
            return;
          }
        }

        if (hasDiscardedAny) {
          discardFailed = 0;
        } else if (++discardFailed > MAX_DISCARD_ATTEMPTS) {
          isFailed = true;
          // Ensure all-or-nothing allocation.
          for (int i = 0; i < destAllocIx; ++i) {
            try {
              deallocate(dest[i]);
            } catch (Throwable t) {
              LlapIoImpl.LOG.info("Failed to deallocate after a partially successful allocate: " + dest[i]);
            }
          }
          //Need to release the un-allocated but reserved memory.
          memoryManager.releaseMemory(dest.length - destAllocIx << allocLog2);
          String msg = "Failed to allocate " + size + "; at " + destAllocIx + " out of "
              + dest.length + " (entire cache is fragmented and locked, or an internal issue)";
          logOomErrorMessage(msg);
          throw new AllocatorOutOfMemoryException(msg);
        }
        // Another thread might have allocated a new arena for us to check
        arenaCount = getArenaCount();
        ++discardsAttempt;
      }
    } finally {
      memoryManager.releaseMemory(memoryForceReleased);
      if (!isFailed && discardsAttempt >= LOG_DISCARD_ATTEMPTS) {
        LlapIoImpl.LOG.info("Allocation of " + dest.length + " buffers of size " + size + " took "
            + discardsAttempt + " attempts to free enough memory; force-released " + memoryForceReleased);
      }
    }
  }
Default max amount of bytes to force evict on failed allocation attempts 16MB. used for testing, for production
please adjust {@link ConfVars.LLAP_ALLOCATOR_MAX_FORCE_EVICTED}
llap-server/src/java/org/apache/hadoop/hive/llap/cache/BuddyAllocator.java