应调用managementfactory.getmemorypoolmxbeans()获取全部实例,因memorypoolmxbean是接口且平台mxbean无单例;各内存池需单独处理getusage()与getpeakusage(),注意getmax()可能为-1,并校验isusagethresholdsupported()后再设阈值。

MemoryPoolMXBean 怎么拿到,别用 ManagementFactory.getPlatformMXBean() 错误类型
直接调用 ManagementFactory.getPlatformMXBean(MemoryPoolMXBean.class) 会抛 IllegalArgumentException —— 因为 MemoryPoolMXBean 是接口,且平台 MXBean 里没有单例实现,它是一组(每个内存池一个实例)。正确做法是通过 ManagementFactory.getMemoryPoolMXBeans() 获取全部实例列表。
- 每个 JVM 内存池(如
"PS Eden Space"、"G1 Old Gen")对应一个独立的MemoryPoolMXBean实例 - 名字和数量取决于 GC 类型:Parallel GC 通常有 3–4 个池,G1 有 5 个左右,ZGC 则只有
"ZHeap"这种统一视图 - 不要在启动时缓存某个池的引用,运行中可能被 GC 策略动态增删(比如 G1 的 Humongous 区可能临时出现)
getUsage() 和 getPeakUsage() 返回值差异直接影响告警逻辑
getUsage() 返回当前已用/最大内存(单位字节),但它的 getMax() 在某些池上恒为 -1(例如 G1 的 "G1 Survivor Space"),意味着“无固定上限”;而 getPeakUsage() 记录的是自 VM 启动以来的历史最高使用量,不会自动重置。
- 做内存水位告警必须用
getUsage().getUsed() / (double) getUsage().getMax(),但得先判断getMax() != -1,否则除零或 NaN - 对
getMax() == -1的池(常见于 Survivor、CodeCache、Metaspace),应改用绝对阈值(如getUsed() > 256 * 1024 * 1024)或参考getUsage().getMax() == -1 ? getPeakUsage().getMax() : getUsage().getMax() -
getPeakUsage()不是实时刷新的,只在每次 GC 后更新,所以不能用来监控秒级抖动
isUsageThresholdSupported() 为 false 时别硬开阈值通知
不是所有内存池都支持阈值回调:isUsageThresholdSupported() 返回 false 的池(如大多数 Survivor Space、Compressed Class Space)调用 setUsageThreshold() 会静默失败,且注册的 NotificationListener 永远不会触发。
- 务必先检查支持性,再设置阈值:
if (pool.isUsageThresholdSupported()) { pool.setUsageThreshold(100 * 1024 * 1024); } - Metaspace 默认不支持 usage threshold(JDK 8u40+ 才支持),但支持
setUsageThreshold()需配合 JVM 参数-XX:+UnlockDiagnosticVMOptions -XX:+ExplicitGCInvokesConcurrent才生效 - 监听器收到
"jvm.memory.threshold.exceeded"事件后,记得手动调用resetUsageThreshold(),否则只触发一次
频繁调用 getUsage() 会不会拖慢应用
会,但程度有限。每次 getUsage() 是本地 JVM 调用,不涉及远程通信,但底层需原子读取 GC 内部计数器,在高并发场景下可能引发缓存行争用(尤其多 socket 机器上)。
立即学习“Java免费学习笔记(深入)”;
- 生产环境建议采样间隔 ≥ 5 秒;低于 1 秒的轮询对吞吐敏感服务(如 Netty 网关)可能增加 1–3% CPU 开销
- 避免在请求处理链路中同步调用(比如 HTTP 接口里每请求查一次),应走异步定时任务或 Metrics Reporter
- 如果只关心堆内存,优先用
Runtime.getRuntime().totalMemory()和freeMemory(),它们比遍历所有MemoryPoolMXBean快 3–5 倍
内存池名是 JVM 实现细节,不同版本、不同 GC 算法下的名称不一致,硬编码字符串匹配(比如 pool.getName().contains("Eden"))极易失效。真要分类,得结合 pool.getType()(Heap/NonHeap)和 pool.isUsageThresholdSupported() 组合判断,而不是依赖名字。










