You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot+Jetty并行上传大文件至S3出现OOM问题求助

大文件流式上传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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:53:14