REST API应用:已下载文件内存清理与InputStream垃圾回收保障问题
REST API下载后清理文件集合及大文件InputStream回收方案
1. 下载完成后从共享集合移除文件的实现
核心要解决的是不能在返回响应后立刻移除文件——因为此时文件流可能还在写入客户端,提前移除会导致IO异常。正确的做法是在文件流完全写入响应后再执行清理,推荐用Spring的StreamingResponseBody实现:
关键思路
- 用
StreamingResponseBody作为响应体,它会在服务器端完成流写入后触发后续逻辑 - 确保共享集合
files4users是线程安全的,比如用ConcurrentHashMap<String, CopyOnWriteArrayList<FileMeta>>替代普通HashMap,避免并发修改问题 - 在流写入完成的逻辑块中,移除对应文件,同时清理空的用户列表
示例代码
@GetMapping("/download/data") public ResponseEntity<StreamingResponseBody> download(@RequestParam String username, @RequestParam String fileKey) { // 从线程安全集合中获取文件元数据(注意:不要存InputStream,只存文件路径/元数据) FileMeta targetFile = files4users.getOrDefault(username, new CopyOnWriteArrayList<>()) .stream() .filter(meta -> meta.getFileKey().equals(fileKey)) .findFirst() .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "文件不存在")); StreamingResponseBody responseBody = outputStream -> { // 用try-with-resources自动关闭流,避免资源泄漏 try (FileInputStream inputStream = new FileInputStream(new File(targetFile.getFilePath()))) { byte[] buffer = new byte[4096]; // 4KB缓冲区平衡性能与内存占用 int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } // 流写入完成,从集合移除文件 files4users.get(username).removeIf(meta -> meta.getFileKey().equals(fileKey)); // 清理空用户列表,释放更多内存 if (files4users.get(username).isEmpty()) { files4users.remove(username); } } catch (IOException e) { throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "文件下载失败", e); } }; return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + targetFile.getFileName() + "\"") .contentType(MediaType.parseMediaType("text/csv")) .body(responseBody); }
2. 500MB大文件InputStream的垃圾回收保障
大文件流如果处理不当,极易导致内存泄漏或OOM,必须做到以下几点:
核心措施
- 绝对不要在共享集合中存InputStream:集合里只存文件元数据(路径、文件名、唯一标识),需要下载时才打开流,用完立刻关闭,避免强引用持有流对象
- 强制关闭流:用try-with-resources语法(Java 7+)自动关闭流,替代手动finally块,确保无论是否异常,流都会被关闭,释放底层文件句柄和内存缓冲区
- 避免流被意外持有:检查代码中是否有其他地方持有该InputStream的强引用(比如全局变量、静态集合),这些会阻碍GC回收
- JVM调优辅助:针对大文件场景,可调整堆内存(如
-Xmx4G),启用G1垃圾回收器(-XX:+UseG1GC),让大对象更快进入老年代并被回收 - 验证回收情况:用JProfiler或VisualVM等工具监控内存,确认InputStream相关对象在下载完成后被及时回收,排查潜在泄漏点
内容的提问来源于stack exchange,提问作者Luke
相关产品推荐
相关产品推荐

