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

Spring Boot内嵌Jetty大文件上传堆溢出问题及流式上传方案咨询

解决大文件流式上传避免OOM的方案

我之前刚好处理过类似的5GB大文件上传场景,你的问题核心很明确:OOM发生在Spring参数绑定阶段,也就是Jetty在解析Multipart请求时,把整个文件都加载到内存里了,根本没等到你的controller方法去处理流。下面分后端配置、前端调整和额外优化点来一步步解决:

一、后端核心配置:让Jetty用磁盘存储Multipart文件

Spring Boot默认用Jetty的Multipart解析器,但默认配置下,小文件会存在内存,大文件才会写到磁盘——但你的错误日志显示,Jetty还是在把文件往内存里塞(ByteArrayOutputStream在扩容),所以必须强制让Jetty把超过阈值的文件直接写到磁盘:

1. 基础配置(application.yml)

先在配置文件里设置Multipart的参数,覆盖默认值:

spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 5GB          # 单个文件最大大小
      max-request-size: 5GB       # 整个请求最大大小
      file-size-threshold: 1MB    # 超过1MB就写到磁盘,而不是内存
      location: ${java.io.tmpdir} # 临时文件存储目录,用系统临时目录就行

2. 自定义Jetty Multipart配置(可选但更稳妥)

如果上面的配置还没生效,可能是Jetty的默认MultiPartConfig没被覆盖,这时候需要自定义Jetty服务器配置:

import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.MultiPartConfig;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;

@Configuration
public class JettyMultipartConfig {

    @Bean
    public JettyServerCustomizer multipartConfigCustomizer() {
        return server -> {
            // 获取Jetty的HTTP配置
            ServerConnector connector = (ServerConnector) server.getConnectors()[0];
            HttpConfiguration httpConfig = connector.getDefaultHttpConfiguration();
            
            // 构建MultiPart配置
            MultiPartConfig.Builder multipartBuilder = new MultiPartConfig.Builder()
                    .setMaxFileSize(DataSize.ofGigabytes(5).toBytes())
                    .setMaxRequestSize(DataSize.ofGigabytes(5).toBytes())
                    .setFileSizeThreshold(DataSize.ofMegabytes(1).toBytes())
                    .setLocation(System.getProperty("java.io.tmpdir"));
            
            httpConfig.setMultiPartConfig(multipartBuilder.build());
        };
    }
}

二、Controller代码优化:确保流式处理

你原来的controller代码其实已经是流式写文件了,但要确保MultipartFile的输入流是从磁盘读取,而不是内存。另外,可以进一步简化代码,用Spring的工具类或者Java NIO来减少手动buffer操作:

import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

@PostMapping("import/upload")
public void importUpload(
        @RequestParam("file") @ApiParam("Файл с данными для импорта") MultipartFile file) throws Exception {
    // 直接用Files.copy流式复制,避免手动写buffer逻辑
    try (InputStream inputStream = file.getInputStream()) {
        Files.copy(inputStream, zipArchive.getFile().toPath(), StandardCopyOption.REPLACE_EXISTING);
    } catch (Exception ex) {
        throw new RuntimeException("Ошибка при загрузке файла: " + ex.getMessage(), ex);
    }
}

这里的关键是:绝对不要调用MultipartFile.getBytes(),这个方法会把整个文件读到内存,一定要用getInputStream()来流式处理。

三、前端调整:确保浏览器流式上传

你的Ant Design Upload代码基本没问题,但可以加几个小优化,确保浏览器不会把整个文件加载到内存:

  1. 不要手动读取文件内容(比如用FileReader),AntD的Upload默认会直接把File对象加到FormData里,浏览器会流式上传,不会占内存。
  2. 显式指定请求头,避免自动适配出问题:
<Upload.Dragger
  style={{paddingLeft: '10px', paddingRight: '10px'}}
  action="api/server/import/upload"
  name={name}
  onChange={this.changeFileState}
  showUploadList={false}
  accept=".zip"
  headers={{
    'Content-Type': 'multipart/form-data'
  }}
>
  <p className="ant-upload-drag-icon">
    <Icon type={icon}/>
  </p>
  <p className="ant-upload-text">{label}</p>
  {this.fileName ? (
    <div>
      <p className="ant-upload-hint">{this.fileName}</p>
      <Progress size="small" status={this.status} percent={this.progress}/>
    </div>
  ) : (
    <p className="ant-upload-hint">{hint}</p>
  )}
</Upload.Dragger>

四、备选方案:分片上传(如果流式上传仍有问题)

如果遇到网络不稳定或者超大文件(比如10GB+),可以考虑分片上传:

  • 前端把文件切成多个小分片(比如100MB/片),依次上传,后端接收后合并。
  • AntD的Upload支持分片上传,只要配置chunk和chunkSize参数:
<Upload.Dragger
  // 其他原有配置
  chunk={true}
  chunkSize={1024 * 1024 * 100} // 100MB每片
>
  {/* 你的UI内容 */}
</Upload.Dragger>

后端需要处理分片的接收、存储和合并逻辑,这会增加复杂度,但能更好地应对极端场景。

最后验证点

  • 上传大文件时,观察系统临时目录(java.io.tmpdir)是否有临时文件生成,且文件大小逐步增长,而不是直接跳到5GB。
  • 监控JVM堆内存,确保上传过程中堆内存不会出现飙升。

内容的提问来源于stack exchange,提问作者Lukyanov Mikhail

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:44:46