I have encountered a memory management problem in a Spring Boot application, and would like to get advice on how to properly clean up direct memory.
Configuration:
- JDK: OpenJDK Runtime Environment (build 21.0.8+9-Ubuntu-0ubuntu122.04.1), OpenJDK 64-Bit Server VM
- Spring Boot: Versions 3.2.6 - 3.4.6
I have the following controller and service:
@RequiredArgsConstructor
@RestController
public class MyEntityController {
private final MyEntityService service;
@PostMapping("/file")
public ResponseEntity<String> addedFileInVersion(MultipartHttpServletRequest request) throws IOException {
return service.addFileVersion(request);
}
}
@Service
@Slf4j
@RequiredArgsConstructor
public class MyEntityService {
@Transactional
public ResponseEntity<String> addFileVersion(MultipartHttpServletRequest request) throws IOException {
try {
log.info("Before reading : {} bytes", getDirectMemoryUsed());
MultipartFile file = request.getFile("file");
if (file == null) {
return ResponseEntity.badRequest().body("The file has not been transferred or the file is empty.");
}
byte[] array = file.getBytes();
String fileName = file.getOriginalFilename();
storeFile(array, fileName);
log.info("After reading : {} bytes", getDirectMemoryUsed());
return ResponseEntity.ok("ok");
} catch (IOException e) {
log.error("Error processing file", e);
return ResponseEntity.badRequest().body("Error when uploading file: " + e.GetMessage());
} finally {
log.info("Finally reading : {} bytes", getDirectMemoryUsed());
}
}
private void storeFile(byte[] array, String path) throws IOException {
String decode = UriUtils.decode(path, StandardCharsets.UTF_8.toString());
synchronized (this) {
Path savedPath = Paths.get("/storage/tmp", decode);
if (Files.notExists(savedPath.getParent())) {
Files.createDirectories(savedPath.getParent());
}
Files.write(savedPath, array);
}
}
private double getDirectMemoryUsed() {
List<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class);
for (BufferPoolMXBean pool : bufferPoolMXBeans) {
if ("direct".equals(pool.getName())) {
return pool.getMemoryUsed();
}
}
return 0;
}
}
When sending files, I observe an increase in the use of direct memory, which does not decrease after processing the file. The logs show the following:
| Step | Memory |
|---|---|
| Before reading | 24576.0 bytes |
| After reading | 9.58496462E8 bytes |
| Finally reading | 9.58496462E8 bytes |
| Before reading | 9.58512846E8 bytes |
| After reading | 1.916984732E9 bytes |
| Finally reading | 1.916984732E9 bytes |
It can be seen that after processing the file, the direct memory is not released.
I understand that using file.getBytes() (as well as readAllBytes()) is not an optimal solution for working with files, as it loads the entire file into memory. I also know that direct memory is located outside the JVM heap and is not directly managed by the garbage collector, and using Cleaner is not recommended in most cases.
In this regard, I have a question: what are alternative and recommended approaches for processing files in Spring Boot in order to effectively manage direct memory and avoid problems with OutOfMemoryError?
What strategies can be used instead of loading the entire file into memory, and how to properly free up direct memory in such scenarios?