JConsole无法显示非堆直接缓冲区内存使用量问题
问题根因
使用ByteBuffer.allocateDirect(int)申请非堆直接缓冲区、持续分配直到抛出java.lang.OutOfMemoryError: Direct buffer memory时,JConsole默认非堆内存面板不展示直接缓冲区已用内存,是JConsole默认监控项的覆盖范围限制导致的,和直接内存本身的分配逻辑无关:
- 直接缓冲区是JVM通过本地方法向操作系统申请的堆外内存,既不属于JVM堆内存,也不属于元空间、代码缓存这类JVM内置管理的非堆内存池,自然不会出现在JConsole默认的「非堆内存」统计曲线里
- JConsole默认内存面板采集的是
MemoryMXBean和各个内置MemoryPoolMXBean的指标,而直接缓冲区的统计数据是单独注册在java.nio.BufferPool的MBean下的,默认不会在内存首页展示。
持续分配直接内存最终抛出直接内存OOM是符合预期的:如果没有手动设置-XX:MaxDirectMemorySize参数,JVM允许的最大直接内存上限默认和-Xmx设置的堆最大值一致,持续分配不释放触达上限就会抛出对应错误。
可通过如下代码复现该现象:
import java.nio.ByteBuffer; import java.util.LinkedList; public class BufferTest { public static void main(String[] args) throws InterruptedException { LinkedList<ByteBuffer> ll = new LinkedList<ByteBuffer>(); int i = 0; while (true) { ByteBuffer buffer = ByteBuffer.allocateDirect(100 * 1024 * 1024); while (buffer.hasRemaining()) { buffer.put((byte) 1); } ll.add(buffer); System.out.println(i++ + "\t" + buffer.isDirect() + "\t" + buffer.limit()); Thread.sleep(1000); } } }
注意:代码中循环写入字节填充buffer的写法是必要的,部分操作系统下仅调用
allocateDirect只会做虚拟内存映射,不实际占用物理内存,填充操作能保证统计到的内存占用和实际分配值一致。
JConsole查看直接缓冲区占用的操作步骤
不需要安装额外插件,在JConsole里就能看到实时直接内存统计:
- 连接目标Java进程后,切换到顶部的MBean标签页
- 在左侧树形目录依次展开
java.nio.BufferPool->direct节点 - 右侧属性栏就能看到三个核心实时指标:
Count:当前已分配未回收的直接缓冲区对象数量MemoryUsed:已使用的直接内存总字节数,和实际分配的内存大小完全对应TotalCapacity:所有存活直接缓冲区的总容量
如果需要长期监控直接内存变化趋势,也可以选择两种方案:
- 直接在业务代码中遍历
BufferPoolMXBean获取direct类型的内存池指标,定时打印日志或对接监控系统 - 用JDK自带的飞行记录器(JFR)录制运行时数据,在内存分析视图里可以直接看到直接内存的时间维度变化曲线
内容的提问来源于stack exchange,提问作者Machi
相关产品推荐
相关产品推荐

