docs: private static MemoryManager createMemoryManager(
  			TaskManagerServicesConfiguration taskManagerServicesConfiguration,
  			long freeHeapMemoryWithDefrag,
  			long maxJvmHeapMemory) throws Exception {
  		// computing the amount of memory to use depends on how much memory is available
  		// it strictly needs to happen AFTER the network stack has been initialized
  
  		// check if a value has been configured
  		long configuredMemory = taskManagerServicesConfiguration.getConfiguredMemory();
  
  		MemoryType memType = taskManagerServicesConfiguration.getMemoryType();
  
  		final long memorySize;
  
  		boolean preAllocateMemory = taskManagerServicesConfiguration.isPreAllocateMemory();
  
  		if (configuredMemory > 0) {
  			if (preAllocateMemory) {
  				LOG.info("Using {} MB for managed memory." , configuredMemory);
  			} else {
  				LOG.info("Limiting managed memory to {} MB, memory will be allocated lazily." , configuredMemory);
  			}
  			memorySize = configuredMemory << 20; // megabytes to bytes
  		} else {
  			// similar to #calculateNetworkBufferMemory(TaskManagerServicesConfiguration tmConfig)
  			float memoryFraction = taskManagerServicesConfiguration.getMemoryFraction();
  
  			if (memType == MemoryType.HEAP) {
  				// network buffers allocated off-heap -> use memoryFraction of the available heap:
  				long relativeMemSize = (long) (freeHeapMemoryWithDefrag * memoryFraction);
  				if (preAllocateMemory) {
  					LOG.info("Using {} of the currently free heap space for managed heap memory ({} MB)." ,
  						memoryFraction , relativeMemSize >> 20);
  				} else {
  					LOG.info("Limiting managed memory to {} of the currently free heap space ({} MB), " +
  						"memory will be allocated lazily." , memoryFraction , relativeMemSize >> 20);
  				}
  				memorySize = relativeMemSize;
  			} else if (memType == MemoryType.OFF_HEAP) {
  				// The maximum heap memory has been adjusted according to the fraction (see
  				// calculateHeapSizeMB(long totalJavaMemorySizeMB, Configuration config)), i.e.
  				// maxJvmHeap = jvmTotalNoNet - jvmTotalNoNet * memoryFraction = jvmTotalNoNet * (1 - memoryFraction)
  				// directMemorySize = jvmTotalNoNet * memoryFraction
  				long directMemorySize = (long) (maxJvmHeapMemory / (1.0 - memoryFraction) * memoryFraction);
  				if (preAllocateMemory) {
  					LOG.info("Using {} of the maximum memory size for managed off-heap memory ({} MB)." ,
  						memoryFraction, directMemorySize >> 20);
  				} else {
  					LOG.info("Limiting managed memory to {} of the maximum memory size ({} MB)," +
  						" memory will be allocated lazily.", memoryFraction, directMemorySize >> 20);
  				}
  				memorySize = directMemorySize;
  			} else {
  				throw new RuntimeException("No supported memory type detected.");
  			}
  		}
  
  		// now start the memory manager
  		final MemoryManager memoryManager;
  		try {
  			memoryManager = new MemoryManager(
  				memorySize,
  				taskManagerServicesConfiguration.getNumberOfSlots(),
  				taskManagerServicesConfiguration.getNetworkConfig().networkBufferSize(),
  				memType,
  				preAllocateMemory);
  		} catch (OutOfMemoryError e) {
  			if (memType == MemoryType.HEAP) {
  				throw new Exception("OutOfMemory error (" + e.getMessage() +
  					") while allocating the TaskManager heap memory (" + memorySize + " bytes).", e);
  			} else if (memType == MemoryType.OFF_HEAP) {
  				throw new Exception("OutOfMemory error (" + e.getMessage() +
  					") while allocating the TaskManager off-heap memory (" + memorySize +
  					" bytes).Try increasing the maximum direct memory (-XX:MaxDirectMemorySize)", e);
  			} else {
  				throw e;
  			}
  		}
  		return memoryManager;
  	}