Spring+Angular项目中如何通过base64传输大文件并解决堆内存溢出问题
内存溢出问题优化方案
问题根因
现有实现采用全量加载逻辑,先将完整文件读取到内存字节数组中,再一次性编码为全量Base64字符串,大文件场景下字节数组、编码后的Base64字符串两个大对象会直接占满JVM堆内存,触发OOM错误;同时loadAll方法批量加载文件夹内所有文件的全量Base64内容存入集合,进一步放大了内存占用问题。
核心优化方案
1. 流式编码替代全量加载
用分段读取+流式编码逻辑,避免将完整文件和完整Base64字符串一次性加载到内存,Java 8自带的Base64.Encoder提供wrap()方法可直接包装输出流,实现边读取文件边编码输出。
优化后的编码方法示例:
public static String encodeFileToBase64Binary(String fileName) { try (InputStream inputStream = new FileInputStream(fileName); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); OutputStream base64OutputStream = Base64.getEncoder().wrap(outputStream)) { // 8k缓冲区,可根据实际场景调整为4k~16k byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { base64OutputStream.write(buffer, 0, bytesRead); } base64OutputStream.flush(); return outputStream.toString(StandardCharsets.US_ASCII); } catch (IOException e) { logger.debug("errore encodeFileToBase64Binary, ", e); throw new EFWMException(e); } }
2. 避免批量返回全量文件内容
原loadAll方法不要返回文件Base64内容,仅返回文件名、文件类型等元数据,前端需要查看/下载具体文件时,再调用单文件接口获取对应Base64内容,避免同时持有多个大文件的Base64字符串。
优化后的loadAll方法示例:
@Override public List<FileDto> loadAll() { List<FileDto> files = new ArrayList<FileDto>(); File folder = new File("uploads"+ File.separator +"adminfolder"); File[] listOfFiles = folder.listFiles(); // 空文件夹判空处理,避免空指针 if (listOfFiles == null) { return files; } for (File file : listOfFiles) { // 跳过子文件夹,只处理文件 if (file.isFile()) { String tipoFile = BufferedImageUtility.getMimeType(file.getAbsolutePath()); String estensione = ""; // 不加载Base64内容,仅返回元数据 files.add(new FileDto("", null, file.getName(), tipoFile, estensione)); } } return files; }
3. 超大文件场景采用流式响应
如果单文件体积超过100M,建议用Spring的StreamingResponseBody实现边编码边响应,不需要在内存中生成完整的Base64字符串即可返回给前端,内存占用仅为缓冲区大小。
单文件流式接口示例:
@GetMapping("/file/{fileName}/base64") public ResponseEntity<StreamingResponseBody> getFileBase64(@PathVariable String fileName) { File file = new File("uploads" + File.separator + "adminfolder" + File.separator + fileName); if (!file.exists() || file.isDirectory()) { return ResponseEntity.notFound().build(); } String mimeType = BufferedImageUtility.getMimeType(file.getAbsolutePath()); StreamingResponseBody responseBody = outputStream -> { try (InputStream inputStream = new FileInputStream(file); OutputStream base64Output = Base64.getEncoder().wrap(outputStream)) { byte[] buffer = new byte[8192]; int read; while ((read = inputStream.read(buffer)) != -1) { base64Output.write(buffer, 0, read); } } }; return ResponseEntity.ok() .contentType(MediaType.parseMediaType(mimeType)) .body(responseBody); }
附加优化建议
- 可适当调大JVM堆内存,启动参数添加
-Xms2G -Xmx4G,根据服务器硬件配置调整阈值 - 若架构允许优先选择二进制流传输文件,相比Base64可减少33%的体积开销,大幅提升传输效率
- 高频访问的文件可新增本地缓存逻辑,已经生成过的Base64内容不用重复编码,降低CPU和IO开销
内容的提问来源于stack exchange,提问作者Maz
相关产品推荐
相关产品推荐

