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

从Azure Blob存储下载大文件时如何避免OutOfMemoryError?

解决方案

核心问题分析

当前代码出现OOM的根源有两个:

  1. 将整个文件全量加载到内存(ByteArrayOutputStream转byte[]),大文件直接超出JVM堆内存上限;
  2. downloadRange方法参数错误:第二个参数应为结束偏移量,原代码传入文件总大小,导致每次循环重复下载从当前位置到文件末尾的内容,内存占用呈倍数增长。

方案1:直接写入响应流(推荐,彻底解决OOM)

无需新增库,修改代码将文件块直接写入HTTP响应流,避免全量加载到内存:

主方法修改:

public void getFileBytes(@RequestBody ZeusRequestVO<String> request, HttpServletResponse response) {
    documentService.downloadFileToResponse(request.getPayload(), response);
}

下载逻辑修改:

public void downloadFileToResponse(String blobName, HttpServletResponse response) {
    long chunkSize = 10 * 1024 * 1024; // 10MB分块
    CloudBlockBlob blob = connectAndgetCloudBlockBlob(blobName);
    
    try {
        if (blob.exists()) {
            blob.downloadAttributes();
            long fileSize = blob.getProperties().getLength();
            
            // 设置响应头,告知客户端文件信息
            response.setContentType(blob.getProperties().getContentType());
            response.setHeader("Content-Disposition", "attachment; filename=\"" + blobName + "\"");
            response.setContentLengthLong(fileSize);
            
            try (OutputStream outputStream = response.getOutputStream()) {
                for (long i = 0; i < fileSize; i += chunkSize) {
                    // 计算当前块的合法结束偏移量,避免超出文件范围
                    long endOffset = Math.min(i + chunkSize - 1, fileSize - 1);
                    blob.downloadRange(i, endOffset, outputStream);
                    outputStream.flush(); // 实时输出,减少缓冲区堆积
                }
            }
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    } catch (StorageException | IOException e) {
        throw new GenericException(e, BusinesErrorEnum.AZURE_BLOB_STORAGE_EXCEPTION);
    }
}

方案2:修正原下载逻辑的参数错误(仅缓解,仍有OOM风险)

如果业务必须将文件加载到内存返回,先修正downloadRange的参数错误,避免重复下载导致内存翻倍:

public ByteArrayOutputStream downloadFileByChunks(String blobName) {
    long file_size = 0;
    long chunkSize = 10 * 1024 * 1024;
    CloudBlockBlob blob = connectAndgetCloudBlockBlob(blobName);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
    try {
        if (blob.exists()) {
            blob.downloadAttributes();
            file_size = blob.getProperties().getLength();
            
            for (long i = 0; i < file_size; i += chunkSize) {
                // 正确计算结束偏移:当前块最后一个字节的索引
                long endOffset = Math.min(i + chunkSize - 1, file_size - 1);
                blob.downloadRange(i, endOffset, baos);
            }
        }
    } catch (StorageException e) {
        throw new GenericException(e, BusinesErrorEnum.AZURE_BLOB_STORAGE_EXCEPTION);
    }
    return baos;
}

注意:此方案仍会将全量文件加载到内存,仅适用于文件大小不超过JVM堆内存的场景。


方案3:临时文件中转(内存不足时的备选)

若无法直接写入响应流,可将文件下载到磁盘临时文件,再通过临时文件输出,避免占用过多内存:

public void getFileBytes(@RequestBody ZeusRequestVO<String> request, HttpServletResponse response) throws IOException {
    File tempFile = documentService.downloadFileToTemp(request.getPayload());
    try {
        Files.copy(tempFile.toPath(), response.getOutputStream());
    } finally {
        tempFile.delete(); // 清理临时文件
    }
}

public File downloadFileToTemp(String blobName) {
    long chunkSize = 10 * 1024 * 1024;
    CloudBlockBlob blob = connectAndgetCloudBlockBlob(blobName);
    File tempFile = File.createTempFile("azure_blob_", ".tmp");
    
    try (FileOutputStream fos = new FileOutputStream(tempFile)) {
        if (blob.exists()) {
            blob.downloadAttributes();
            long fileSize = blob.getProperties().getLength();
            
            for (long i = 0; i < fileSize; i += chunkSize) {
                long endOffset = Math.min(i + chunkSize - 1, fileSize - 1);
                blob.downloadRange(i, endOffset, fos);
                fos.flush();
            }
        }
    } catch (Exception e) {
        tempFile.delete();
        throw new GenericException(e, BusinesErrorEnum.AZURE_BLOB_STORAGE_EXCEPTION);
    }
    return tempFile;
}

内容的提问来源于stack exchange,提问作者Gonzalo León

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:25:50