分割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。
- 上传完成后手动删除临时文件,避免占用磁盘空间。
注意事项
- S3分片上传要求:除最后一个分片外,其他分片大小必须≥5MB,你的10MB设置符合要求。
- 务必处理上传异常,及时调用
abortMultipartUpload取消未完成的会话,避免S3存储残留分片产生不必要的费用。 - 如果使用AWS SDK v1,API逻辑类似,仅需调整对应类名和方法调用。
内容的提问来源于stack exchange,提问作者lari
相关产品推荐
相关产品推荐

