public long freeMemory() {
   long memoryFreed = 0;
   for (MemoryBlock block : allocatedPages) {
     memoryManager.freePage(block);
     shuffleMemoryManager.release(block.size());
     memoryFreed += block.size();
   }
   allocatedPages.clear();
   currentPage = null;
   currentPagePosition = -1;
   freeSpaceInCurrentPage = 0;
   return memoryFreed;
 }
  /**
   * Allocates more memory in order to insert an additional record. This will request additional
   * memory from the {@link ShuffleMemoryManager} and spill if the requested memory can not be
   * obtained.
   *
   * @param requiredSpace the required space in the data page, in bytes, including space for storing
   *     the record size.
   */
  private void allocateSpaceForRecord(int requiredSpace) throws IOException {
    // TODO: merge these steps to first calculate total memory requirements for this insert,
    // then try to acquire; no point in acquiring sort buffer only to spill due to no space in the
    // data page.
    if (!sorter.hasSpaceForAnotherRecord()) {
      logger.debug("Attempting to expand sort pointer array");
      final long oldPointerArrayMemoryUsage = sorter.getMemoryUsage();
      final long memoryToGrowPointerArray = oldPointerArrayMemoryUsage * 2;
      final long memoryAcquired = shuffleMemoryManager.tryToAcquire(memoryToGrowPointerArray);
      if (memoryAcquired < memoryToGrowPointerArray) {
        shuffleMemoryManager.release(memoryAcquired);
        spill();
      } else {
        sorter.expandPointerArray();
        shuffleMemoryManager.release(oldPointerArrayMemoryUsage);
      }
    }

    if (requiredSpace > freeSpaceInCurrentPage) {
      logger.trace(
          "Required space {} is less than free space in current page ({})",
          requiredSpace,
          freeSpaceInCurrentPage);
      // TODO: we should track metrics on the amount of space wasted when we roll over to a new page
      // without using the free space at the end of the current page. We should also do this for
      // BytesToBytesMap.
      if (requiredSpace > PAGE_SIZE) {
        throw new IOException(
            "Required space " + requiredSpace + " is greater than page size (" + PAGE_SIZE + ")");
      } else {
        final long memoryAcquired = shuffleMemoryManager.tryToAcquire(PAGE_SIZE);
        if (memoryAcquired < PAGE_SIZE) {
          shuffleMemoryManager.release(memoryAcquired);
          spill();
          final long memoryAcquiredAfterSpilling = shuffleMemoryManager.tryToAcquire(PAGE_SIZE);
          if (memoryAcquiredAfterSpilling != PAGE_SIZE) {
            shuffleMemoryManager.release(memoryAcquiredAfterSpilling);
            throw new IOException("Unable to acquire " + PAGE_SIZE + " bytes of memory");
          }
        }
        currentPage = memoryManager.allocatePage(PAGE_SIZE);
        currentPagePosition = currentPage.getBaseOffset();
        freeSpaceInCurrentPage = PAGE_SIZE;
        allocatedPages.add(currentPage);
      }
    }
  }
  /** Write a record to the sorter. */
  public void insertRecord(
      Object recordBaseObject, long recordBaseOffset, int lengthInBytes, long prefix)
      throws IOException {
    // Need 4 bytes to store the record length.
    final int totalSpaceRequired = lengthInBytes + 4;
    if (!haveSpaceForRecord(totalSpaceRequired)) {
      allocateSpaceForRecord(totalSpaceRequired);
    }

    final long recordAddress =
        memoryManager.encodePageNumberAndOffset(currentPage, currentPagePosition);
    final Object dataPageBaseObject = currentPage.getBaseObject();
    PlatformDependent.UNSAFE.putInt(dataPageBaseObject, currentPagePosition, lengthInBytes);
    currentPagePosition += 4;
    PlatformDependent.copyMemory(
        recordBaseObject, recordBaseOffset, dataPageBaseObject, currentPagePosition, lengthInBytes);
    currentPagePosition += lengthInBytes;
    freeSpaceInCurrentPage -= totalSpaceRequired;
    sorter.insertRecord(recordAddress, prefix);
  }