1

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?

0

1 Answer 1

0

How about using the MultipartFile.transferTo(Path dest) method?

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html

This method reads and writes files using a buffer.

//byte[] array = file.getBytes();
String fileName = file.getOriginalFilename();
//storeFile(array, fileName);
file.transferTo(fileName);
log.info("After reading : {} bytes", getDirectMemoryUsed());
return ResponseEntity.ok("ok");
Sign up to request clarification or add additional context in comments.

1 Comment

transferTo() is a good solution, but it doesn't really suit me. because I need byte[]. the addFileVersion method in this case is a simplified example. I have much more of it, and it passes byte[] to several other methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.