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

分割20GB+大文件时遭遇Java heap space错误的技术求助

解决大文件分片上传S3时的Java堆内存溢出问题

你的代码核心问题是将所有文件分片都存储在内存的List<byte[]>中,20GB的文件会占用至少20GB堆内存,远超过JVM默认堆容量,必然触发OutOfMemoryError。以下是两种高效的解决方案:

方案一:边读边上传(最省内存,推荐)

直接利用AWS SDK的分片上传API,读取一个分片就立即上传,不需要在内存中缓存所有分片。以下是基于AWS SDK v2的实现:

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public void uploadLargeFileToS3(String bucketName, String s3ObjectKey) throws IOException {
    File targetFile = new File(backupPath);
    long totalFileSize = targetFile.length();
    int chunkSize = 10 * 1024 * 1024; // 10MB分片大小(符合S3分片要求:最小5MB,最大5GB)
    long totalParts = (totalFileSize + chunkSize - 1) / chunkSize;

    S3Client s3Client = S3Client.create();

    // 初始化分片上传会话
    InitiateMultipartUploadRequest initRequest = InitiateMultipartUploadRequest.builder()
            .bucket(bucketName)
            .key(s3ObjectKey)
            .build();
    InitiateMultipartUploadResponse initResponse = s3Client.initiateMultipartUpload(initRequest);
    String uploadId = initResponse.uploadId();

    List<CompletedPart> completedParts = new ArrayList<>();

    try (FileInputStream fis = new FileInputStream(targetFile)) {
        byte[] buffer = new byte[chunkSize];
        int bytesRead;
        int currentPartNumber = 1;

        while ((bytesRead = fis.read(buffer)) > 0) {
            // 构造分片上传请求
            UploadPartRequest uploadRequest = UploadPartRequest.builder()
                    .bucket(bucketName)
                    .key(s3ObjectKey)
                    .uploadId(uploadId)
                    .partNumber(currentPartNumber)
                    .contentLength((long) bytesRead)
                    .build();

            // 上传当前分片
            s3Client.uploadPart(uploadRequest, RequestBody.fromBytes(buffer, 0, bytesRead));

            // 获取分片ETag并记录
            ListPartsRequest listPartsRequest = ListPartsRequest.builder()
                    .bucket(bucketName)
                    .key(s3ObjectKey)
                    .uploadId(uploadId)
                    .partNumberMarker(currentPartNumber)
                    .maxParts(1)
                    .build();
            String eTag = s3Client.listParts(listPartsRequest).parts().get(0).eTag();

            completedParts.add(CompletedPart.builder()
                    .partNumber(currentPartNumber)
                    .eTag(eTag)
                    .build());

            currentPartNumber++;
        }

        // 完成分片上传
        CompleteMultipartUploadRequest completeRequest = CompleteMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(s3ObjectKey)
                .uploadId(uploadId)
                .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build())
                .build();
        s3Client.completeMultipartUpload(completeRequest);
    } catch (Exception e) {
        // 上传失败时取消会话,避免S3残留分片产生费用
        AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(s3ObjectKey)
                .uploadId(uploadId)
                .build();
        s3Client.abortMultipartUpload(abortRequest);
        throw new IOException("大文件分片上传失败", e);
    }
}

方案优势:

  • 内存仅占用一个10MB的缓冲区和少量分片元数据,完全避免堆内存溢出。
  • 减少磁盘IO开销,不需要额外存储分片文件。

方案二:用临时文件存储分片(需预分片的场景)

如果业务需要先完成分片再统一上传,可以将分片写入临时文件,而非内存数组:

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public List<File> splitFileToTempChunks() throws IOException {
    File targetFile = new File(backupPath);
    int chunkSize = 10 * 1024 * 1024; // 10MB分片
    List<File> chunkFiles = new ArrayList<>();

    try (FileInputStream fis = new FileInputStream(targetFile)) {
        byte[] buffer = new byte[chunkSize];
        int bytesRead;
        int chunkIndex = 0;

        while ((bytesRead = fis.read(buffer)) > 0) {
            // 创建临时分片文件,JVM退出时自动删除
            File chunkFile = File.createTempFile("file-chunk-", ".tmp");
            chunkFile.deleteOnExit();

            try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
                fos.write(buffer, 0, bytesRead);
            }

            chunkFiles.add(chunkFile);
            chunkIndex++;
        }
        return chunkFiles;
    }
}

使用说明:

  • 上传时逐个读取临时文件内容上传到S3。
  • 上传完成后手动删除临时文件,避免占用磁盘空间。

注意事项

  1. S3分片上传要求:除最后一个分片外,其他分片大小必须≥5MB,你的10MB设置符合要求。
  2. 务必处理上传异常,及时调用abortMultipartUpload取消未完成的会话,避免S3存储残留分片产生不必要的费用。
  3. 如果使用AWS SDK v1,API逻辑类似,仅需调整对应类名和方法调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 04:57:13