SpringBoot+Jetty并行上传大文件至S3出现OOM问题求助
我用SpringBoot(2.x)搭配Jetty Server,尝试直接流式并行上传多个约2.2GB的大文件到S3,但还是出现了内存溢出(Out Of Memory)问题。我没有把InputStream加载到内存里,想知道SpringBoot会不会自动把InputStream加载到内存?
控制器类
@RestController @RequestMapping("/v1/{customerId}/{uniqueDocId}/files") public class FileController { @Autowired private FileService fileService; @PostMapping(produces = "application/json") public ResponseEntity<UploadFileResponse> uploadFile( @RequestParam InputStream file, @RequestHeader(HttpHeaders.CONTENT_LENGTH) final long contentLength) throws IOException { FileUploadInput input = FileUploadInput.builder().file(file).contentLength(contentLength).build(); try { return ResponseEntity.ok(fileService.uploadFile(input)); } finally { if(input.getFile() != null) { input.getFile().close(); } } } }
服务类
@Service public class FileServiceImpl implements FileService { @Autowired private AmazonS3 s3Client; @Override public UploadFileResponse uploadFile(FileUploadInput input) { try { ObjectMetadata objMetadata = new ObjectMetadata(); objMetadata.setContentLength(input.getContentLength()); PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, s3Key, input.getFile(), objMetadata); s3Client.putObject(putObjectRequest); return new UploadFileResponse(...); } catch (Exception e) { throw new RuntimeException("上传失败", e); } } }
application.properties配置
spring.servlet.multipart.max-file-size: 5GB spring.servlet.multipart.max-request-size: 5GB
应用运行硬件信息
内存: 3G 磁盘: 5G
核心问题
我已经采用客户端→SpringBoot→S3的直接流式传输方式,也配置了multipart参数,为什么还是抛出内存溢出异常?我试过把InputStream改成MultipartFile,问题依然存在。
栈追踪信息
2023-07-26T12:51:26.988+05:30 [APP/PROC/WEB/0] [OUT] Exit status 137 (out of memory) 2023-07-26T12:51:26.990+05:30 [CELL/SSHD/0] [OUT] Exit status 0
问题分析与解决方案
1. SpringBoot + Jetty的Multipart默认行为隐患
Jetty的Multipart解析器默认会将小文件内容写入内存,仅当文件超过阈值(默认约1MB)时才写入临时磁盘文件。但并行上传多个2.2GB文件时,即便单个文件触发磁盘存储,若临时文件缓存规则、请求缓冲区未配置到位,或Jetty自身内存参数未调优,仍会导致内存占用飙升。另外,使用@RequestParam InputStream file时,SpringBoot通过MultipartResolver解析请求体,若未明确禁用内存存储,解析阶段仍可能占用大量堆内存。
2. AWS S3客户端的潜在内存消耗
你当前使用的putObject(InputStream)方法,若未配置分块上传,S3客户端可能会在内存中缓冲大量数据(尤其是网络较慢时);即便设置了contentLength,客户端仍可能尝试读取流来校验,间接占用内存。
3. 具体优化方案
(1)强制Jetty将大文件写入磁盘
调整SpringBoot的Multipart配置,明确内存阈值与临时文件路径,避免大文件占用堆内存:
# 超过1MB的文件直接写入临时磁盘 spring.servlet.multipart.file-size-threshold=1MB # 指定临时文件存储路径(确保磁盘有足够空间) spring.servlet.multipart.location=/tmp/uploads # 禁用延迟解析,确保请求解析时直接处理文件 spring.servlet.multipart.resolve-lazily=false
(2)启用S3分块上传
使用TransferManager自动分块上传(默认5MB/块),避免一次性加载大量数据到内存:
@Bean public TransferManager transferManager(AmazonS3 s3Client) { return TransferManagerBuilder.standard() .withS3Client(s3Client) .withMultipartUploadThreshold(5 * 1024 * 1024) // 超过5MB自动分块 .build(); }
服务类中改用TransferManager:
@Override public UploadFileResponse uploadFile(FileUploadInput input) { try { ObjectMetadata objMetadata = new ObjectMetadata(); objMetadata.setContentLength(input.getContentLength()); Upload upload = transferManager.upload( bucketName, s3Key, input.getFile(), objMetadata ); upload.waitForCompletion(); return new UploadFileResponse(...); } catch (InterruptedException | AmazonServiceException | AmazonClientException e) { throw new RuntimeException("上传失败", e); } finally { try { input.getFile().close(); } catch (IOException e) { // 记录日志或忽略 } } }
(3)调整JVM内存参数
针对3G总内存的应用,合理分配堆内存与直接内存(NIO和S3客户端会用到直接内存):
-Xmx2g -XX:MaxDirectMemorySize=512m
(4)正确处理MultipartFile
若改用MultipartFile,使用try-with-resources确保流自动关闭,避免资源泄漏:
@PostMapping(produces = "application/json") public ResponseEntity<UploadFileResponse> uploadFile( @RequestParam MultipartFile file) throws IOException { try (InputStream inputStream = file.getInputStream()) { FileUploadInput input = FileUploadInput.builder() .file(inputStream) .contentLength(file.getSize()) .build(); return ResponseEntity.ok(fileService.uploadFile(input)); } }
4. 额外排查点
- 检查Jetty线程池配置:并行请求过多时,线程栈内存会占用大量空间,需调整线程池大小;
- 临时文件清理:定期清理临时文件,避免磁盘空间不足引发间接问题;
- 内存监控:用JVisualVM或Arthas监控堆内存与直接内存占用,定位具体泄漏点。
内容的提问来源于stack exchange,提问作者Anshita Singh

