应禁用 inputstream.readallbytes() 处理不可控大流,改用 transferto 或固定缓冲区流式处理,并确保资源由 try-with-resources 管理。

直接调用 InputStream.readAllBytes() 处理超大流,本质是让 JVM 一次性分配与流长度相等的堆内存。1GB 流 → 1GB byte[],远超常见服务堆配置(如 -Xmx512m),必然触发 OutOfMemoryError: Java heap space。这不是参数调优能解决的问题,而是设计层面的误用。
明确拒绝 readAllBytes() 的使用场景
该方法仅适用于已知极小、确定可控的流(如配置文件、小图标响应体),且长度通常不超过几 MB。只要流来源不可控(OSS/MinIO 下载、HTTP 响应、用户上传),就应默认禁用。
- 检查代码中所有
readAllBytes()调用点,替换为流式处理逻辑 - 在团队编码规范中列为“禁止项”,CI 阶段可通过 SpotBugs 或自定义 Checkstyle 规则拦截
- 若必须转字节数组(如加签、加密),先校验流长度(如
Content-Length头)并设硬上限(如 ≤ 10MB),超限直接拒绝
用 transferTo 替代全量加载
InputStream.transferTo(OutputStream) 是 JDK 1.9+ 提供的安全替代方案。它内部使用固定缓冲区(Java 17 默认 8KB),边读边写,全程不缓存全部数据,内存占用恒定。
- 下载文件到磁盘:直接
inputStream.transferTo(Files.newOutputStream(path)) - 转发 HTTP 响应:
inputStream.transferTo(servletResponse.getOutputStream()) - 注意:目标
OutputStream必须支持写入(如FileOutputStream、SocketOutputStream),不适用于需要中间处理的场景
需要中间处理时,坚持小块读取 + 及时消费
当必须解析、转换或校验流内容(如 CSV 解析、JSON 流式反序列化、SHA256 计算),核心是“只留一块,处理一块,丢弃一块”。
- 使用固定大小缓冲区(推荐 8KB–64KB),例如
byte[] buffer = new byte[8192] - 循环
read(buffer),每次拿到实际读取长度len,立即处理buffer[0, len),不累积、不缓存 - 计算哈希时,直接
digest.update(buffer, 0, len);写入文件时,直接outputStream.write(buffer, 0, len) - 避免包装成
ByteArrayOutputStream或StringBuilder等可扩容容器
资源生命周期必须由 try-with-resources 保障
流未关闭不仅导致连接泄漏、句柄耗尽,还会阻碍底层资源(如 OkHttp 的 Buffer、Netty 的 ByteBuf)释放,间接加剧内存压力。
- 所有
InputStream、OutputStream、Reader、Writer必须声明在 try 括号内 - 不要在 catch 块中忽略 close(),也不要在 finally 中裸写 close()(可能抛出新异常掩盖原异常)
- MinIO/OSS 客户端返回的流尤其敏感——底层绑定 HTTP 连接池,不关闭会快速占满
maxIdleConnections
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











