如何解决Multipart文件转发导致的java.lang.OutOfMemoryError问题?
低内存占用的Multipart文件接收与转发方案
问题根源
你当前用file.getResource()获取的是ByteArrayResource,会把整个文件内容加载到JVM堆内存里,大文件高并发场景下必然导致堆内存暴涨,且大对象容易进入老年代,GC无法及时回收,最终耗尽堆空间。
解决方案
1. 接收阶段:避免文件全量加载到内存
在Spring/Spring Boot中,强制所有上传文件直接写入磁盘,彻底避免内存占用:
- Spring Boot项目中,在
application.properties添加配置:
配置后,所有上传的MultipartFile都会对应磁盘上的临时文件,而非内存中的字节数组。spring.servlet.multipart.max-in-memory-size=0 spring.servlet.multipart.location=/path/to/your/temp/dir # 指定临时文件存储目录,需配置磁盘清理策略
2. 转发阶段:流式传输文件,不加载全量到内存
替换file.getResource()为FileSystemResource,直接引用磁盘临时文件,让下游请求时流式读取内容:
try { // 将MultipartFile转存到临时文件(若已在磁盘可跳过此步) File tempFile = File.createTempFile("upload_", ".tmp"); file.transferTo(tempFile); tempFile.deleteOnExit(); // 进程退出时自动删除 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); // 用FileSystemResource替代ByteArrayResource,实现流式传输 body.add("model", new FileSystemResource(tempFile)); HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers); // 发送请求到下游服务(示例用RestTemplate) restTemplate.postForObject("http://downstream-service/upload", requestEntity, String.class); // 请求完成后手动删除临时文件,避免磁盘占用 tempFile.delete(); } catch (IOException e) { // 处理异常逻辑 }
- 若使用
CommonsMultipartFile,可直接获取已存在的磁盘文件路径,省去转存步骤:DiskFileItem fileItem = (DiskFileItem) ((CommonsMultipartFile) file).getFileItem(); File tempFile = fileItem.getStoreLocation(); body.add("model", new FileSystemResource(tempFile));
3. 配置RestTemplate支持流式传输
确保RestTemplate的FormHttpMessageConverter不会将文件内容加载到内存,关闭缓冲:
@Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) { if (converter instanceof FormHttpMessageConverter) { FormHttpMessageConverter formConverter = (FormHttpMessageConverter) converter; formConverter.setBufferRequestBody(false); // 关闭请求体缓冲,保证流式传输 } } return restTemplate; }
4. 进阶:Spring WebFlux全流式处理
针对高并发大文件场景,推荐用Spring WebFlux实现全程非阻塞流式传输,中间服务几乎不占用内存:
// 接收请求的Controller @PostMapping("/upload") public Mono<String> uploadFile(@RequestPart("model") FilePart filePart) { // 用WebClient流式转发到下游服务 return webClient.post() .uri("http://downstream-service/upload") .contentType(MediaType.MULTIPART_FORM_DATA) .body(BodyInserters.fromMultipartData("model", filePart)) .retrieve() .bodyToMono(String.class); } // 配置WebClient @Bean public WebClient webClient() { return WebClient.create(); }
这种方式下,文件数据会从客户端直接流式传输到下游服务,中间节点仅负责转发数据流,内存占用极低。
额外注意事项
- 临时文件目录需配置定期清理策略(比如Linux的
tmpwatch或定时任务),避免磁盘被占满。 - 监控磁盘使用情况,防止临时文件积压导致服务异常。
内容的提问来源于stack exchange,提问作者Anuj Ranaa
相关产品推荐
相关产品推荐

