本质是堆外内存耗尽,需从限制上限(-xx:maxdirectmemorysize)、排查allocatedirect调用源(如netty未release)、改用堆内缓冲(-dio.netty.nopreferdirect=true)或流式小块处理三方面解决。

Java 中处理大字节流时出现 java.lang.OutOfMemoryError: Direct buffer memory,本质是堆外内存(Direct Buffer)被耗尽,而非堆内存不足。它不走 GC 流程,无法靠调大 -Xmx 解决,必须从分配控制、使用方式和释放机制三方面入手。
明确限制直接内存上限
默认情况下,JVM 的最大直接内存 ≈ -Xmx 值,但实际受 -XX:MaxDirectMemorySize 控制。未显式设置时,容易在容器或高并发场景下因系统级限制(如 cgroup)而意外超限。
- 启动时强制设上限:例如
-XX:MaxDirectMemorySize=512m,推荐为堆内存的 1/4~1/2 - 验证当前值:
System.out.println("MaxDirect: " + sun.misc.VM.maxDirectMemory() / 1024 / 1024 + " MB"); - 配合
-XX:+PrintGCDetails观察日志中是否频繁报 DirectBuffer 分配失败
排查并约束高危分配源
堆外内存主要由 NIO 框架(Netty、Undertow)、文件传输、图像库或自定义缓冲池触发。重点检查:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 所有
ByteBuffer.allocateDirect()调用,尤其出现在循环、连接复用、递归逻辑中的位置 - Netty 用户需确认是否启用了
PooledByteBufAllocator,且每次buffer.release()是否成对调用;漏一次即泄漏一块 - 用
jcmd <pid> VM.native_memory summary</pid>(需开启-XX:NativeMemoryTracking=summary)查看 direct memory 实际占用趋势
优先改用堆内缓冲或安全替代方案
不是所有场景都必须用 Direct Buffer。能规避就规避,更可控:
- Netty 场景可加启动参数:
-Dio.netty.noPreferDirect=true,强制使用堆内缓冲 - 文件拷贝、HTTP 响应转发等纯传输场景,用
InputStream.transferTo(OutputStream)—— 它内部用固定大小缓冲区(JDK 17 默认 8KB),全程不缓存全部数据 - 若必须用 Direct Buffer 做中间处理(如加签、解密),务必配合
Cleaner或手动调用((DirectBuffer) buf).cleaner().clean()(注意 JDK 版本兼容性)
流式处理代替全量加载
很多 Direct Buffer 溢出,其实是误用导致的连锁反应。比如先用 readAllBytes() 加载大流到堆内 byte[],再转成 ByteBuffer.wrap() 或 allocateDirect() 复制一份 —— 这既占堆又占堆外。
- 禁用
Files.readAllBytes()、IOUtils.toByteArray()等全量加载 API 处理不可控来源(OSS 下载、用户上传、HTTP 响应) - 坚持“小块读取 + 即时消费”:用
byte[8192]缓冲区循环read(),拿到长度len后立即处理buffer[0, len),不累积、不包装成 ByteArrayOutputStream - 计算哈希、写入磁盘、解析 JSON 等操作,全部基于该缓冲区原地进行
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










