Spring MVC大文件上传易触发OutOfMemoryError,核心解法是禁用multipart自动解析、改用ServletFileUpload流式读取,并结合分片上传+服务端校验与合并机制,辅以Nginx限流、MinIO直传等生产级防护。

Spring MVC 默认使用内存缓冲处理文件上传,大文件容易触发 java.lang.OutOfMemoryError: Java heap space。核心解法是禁用内存缓冲、启用流式读取,并结合分片校验机制保障完整性。
禁用 MultipartFile 内存缓冲,改用流式读取
Spring 默认将上传文件全部加载进内存(StandardServletMultipartResolver 的 resolveMultipart),超过 JVM 堆上限就会 OOM。必须绕过 MultipartFile,直接从请求流中读取原始数据。
- 在
application.properties中关闭自动解析 multipart:
spring.servlet.multipart.enabled=false
- 控制器方法不接收
MultipartFile,改用InputStream或HttpServletRequest:
@PostMapping("/upload")
public ResponseEntity<String> upload(HttpServletRequest request) throws IOException {
// 手动解析 multipart 请求体,逐块读取,不缓存全量
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) {
try (InputStream is = item.getInputStream()) {
// 直接流式写入磁盘或对象存储,例如:
Files.copy(is, Paths.get("/data/uploads/", item.getName()),
StandardCopyOption.REPLACE_EXISTING);
}
}
}
return ResponseEntity.ok("success");
}- 务必设置 Tomcat 的
maxSwallowSize(默认 -1 即不限制)和maxHttpHeaderSize,避免因 header 过大被截断;同时调整 JVM 堆大小(如-Xmx2g)仅作兜底,不依赖它扛大文件。
实现客户端分片 + 服务端合并与校验
分片本身不能解决 OOM,但配合服务端流式接收和独立校验,可规避单次大请求、支持断点续传、并确保最终文件一致性。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 前端按固定大小(如 5MB)切片,每片携带唯一标识(
fileId)、分片序号(chunkIndex)、总片数(totalChunks)、文件名和 MD5(整个文件的) - 后端接收每片时做三项检查:
• 校验该分片的 内容 MD5(防止传输篡改)
• 检查 fileId + chunkIndex 是否已存在(幂等写入)
• 记录已上传分片索引到 Redis 或 DB,用于后续合并判断
- 合并逻辑放在“所有分片上传完成”后的回调接口中,不阻塞上传过程:
@PostMapping("/merge")
public ResponseEntity<String> merge(@RequestBody MergeRequest req) {
String fileId = req.getFileId();
int total = req.getTotalChunks();
<pre class="brush:php;toolbar:false;">// 查询 Redis 中已上传的分片索引集合
Set<String> uploaded = redisTemplate.opsForSet().members("chunks:" + fileId);
if (uploaded.size() != total) {
return ResponseEntity.badRequest().body("missing chunks");
}
// 按序号升序读取每个分片文件,流式合并到目标路径
try (RandomAccessFile raf = new RandomAccessFile(targetFile, "rw")) {
for (int i = 0; i < total; i++) {
File chunk = new File("/tmp/chunks/", fileId + "_" + i);
try (FileInputStream fis = new FileInputStream(chunk)) {
raf.seek((long) i * CHUNK_SIZE); // 假设等长分片
fis.transferTo(raf.getChannel());
}
}
}
// 合并后校验最终文件 MD5 是否等于前端传来的 fileMd5
String actualMd5 = DigestUtils.md5Hex(Files.newInputStream(targetFile));
if (!actualMd5.equals(req.getFileMd5())) {
throw new RuntimeException("MD5 mismatch after merge");
}
return ResponseEntity.ok("merged");}
补充关键防护措施
仅靠分片和流式还不够,需叠加运行时控制与资源隔离。
-
请求体大小限制前移:在 Web 服务器层(如 Nginx)配置
client_max_body_size 2G,早于 Spring 拦截超大请求,避免进入应用容器 -
上传会话隔离:为每个
fileId分配独立临时目录,避免并发写冲突;用ReentrantLock或 Redis 分布式锁保护合并操作 - 清理策略:设置未完成上传的分片 24 小时自动清理(Redis key 设置 TTL 或定时扫描本地临时目录)
- 监控与告警:记录单次上传耗时、分片失败率、合并成功率,异常时触发告警
推荐技术组合
不建议纯靠 Spring MVC 实现重型上传。生产环境应组合使用:
- 前端:WebUploader / Uppy.js(支持分片、断点、秒传、进度)
- 网关层:Nginx(限流、超时、反向代理、静态分片路由)
- 存储层:MinIO / 阿里云 OSS(直接预签名 URL 上传,彻底绕过应用内存)
- 校验层:服务端计算 SHA-256(比 MD5 更安全),合并后比对 ETag(OSS)或自定义 checksum

















