- 可观测性
- 后端
【免费下载链接】metrics
:chart_with_upwards_trend: Capturing JVM- and application-level metrics. So you know what's going on.
metrics-jvm是 Dropwizard Metrics(当前仓库为io.dropwizard.metrics,模块坐标见 metrics-jvm/pom.xml)中的 JVM 集成模块,它内置了一批可直接复用的Gauge与MetricSet,让开发者无需编写任何 JMX 代码就能把 JVM 内部运行状态接入统一的 Metrics 体系。本文以官方手册 docs/source/manual/jvm.rst 为主线,结合模块源码(metrics-jvm/src/main/java/com/codahale/metrics/jvm)讲解每个指标集的采集范围、指标命名、平台限制与典型接入方式,帮助读者在生产环境中快速构建可观测的 JVM 监控面板。
metrics-jvm 能采集哪些指标
根据手册定义,metrics-jvm模块覆盖五类 JVM 关键运行数据:
- 垃圾回收(GC):所有已发现垃圾收集器的运行次数与累计耗时;
- 内存:所有内存池(Memory Pool)的使用情况,包括堆外(off-heap)内存;
- 线程:按线程状态(
NEW、RUNNABLE、BLOCKED、WAITING等)的计数分解,以及死锁(deadlock)检测; - 文件描述符:已打开文件描述符占总数的比例;
- 缓冲池(Buffer Pool):堆外缓冲池的大小与利用率。
这些能力全部由com.codahale.metrics.jvm包下的若干MetricSet实现提供。MetricSet是 metrics-core 定义的一种分组机制,它把一组相关的Metric(通常是一堆Gauge)打包成可整体注册的单元,库作者因此可以为一大类功能提供"一站式"的插桩入口。手册原文指出该模块包含"a number of reusable gauges and metric sets",这正是其设计核心:复用。
快速接入:一条语句注册全部 JVM 指标
接入方式极其简单——只需要把各个MetricSet通过MetricRegistry.registerAll()注册到应用的 registry 即可:
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.jvm.BufferPoolMetricSet; import com.codahale.metrics.jvm.ClassLoadingGaugeSet; import com.codahale.metrics.jvm.FileDescriptorRatioGauge; import com.codahale.metrics.jvm.GarbageCollectorMetricSet; import com.codahale.metrics.jvm.JvmAttributeGaugeSet; import com.codahale.metrics.jvm.MemoryUsageGaugeSet; import com.codahale.metrics.jvm.ThreadStatesGaugeSet; MetricRegistry registry = new MetricRegistry(); registry.registerAll(new GarbageCollectorMetricSet()); registry.registerAll(new MemoryUsageGaugeSet()); registry.registerAll(new ThreadStatesGaugeSet()); registry.registerAll(new FileDescriptorRatioGauge()); registry.registerAll(new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer())); registry.registerAll(new ClassLoadingGaugeSet()); registry.registerAll(new JvmAttributeGaugeSet());注册之后,所有指标就以标准 Metrics 形式进入 registry,可被 JMX Reporter、Console、CSV、SLF4J 等报告器导出,或通过 MetricsServlet 以 JSON 方式暴露。metrics-jvm的 pom.xml 显示它只依赖metrics-core与slf4j-api,没有任何第三方运行库负担,可以直接作为metrics-core的伴生模块引入。
垃圾回收指标:GarbageCollectorMetricSet
GarbageCollectorMetricSet.java 为每个已发现的GarbageCollectorMXBean注册两个 gauge:
name.count:该 GC 的运行次数(对应getCollectionCount());name.time:该 GC 累计耗时,单位为毫秒(对应getCollectionTime())。
其中name取自 GC 的实际名称,且源码中用WHITESPACE.matcher(gc.getName()).replaceAll("-")把名称中的空白字符替换为-。例如 HotSpot JVM 常见的 GC 名称 "Copy"、"MarkSweepCompact"、"PS Scavenge"、"PS MarkSweep",注册后会产生形如PS-Scavenge.count、PS-Scavenge.time这样的指标名。默认构造器通过ManagementFactory.getGarbageCollectorMXBeans()发现当前 JVM 的所有 GC;也提供了接受Collection<GarbageCollectorMXBean>的构造器,便于在测试中注入 mock 对象(见 GarbageCollectorMetricSetTest.java)。
内存指标:MemoryUsageGaugeSet
MemoryUsageGaugeSet.java 是内容最丰富的指标集,数据来源是MemoryMXBean与全部MemoryPoolMXBean。它注册的指标分四个层级:
总量层(total),将堆与非堆相加:
total.init、total.used、total.max、total.committed
注意源码对total.max做了特殊处理:当NonHeapMemoryUsage.getMax() == -1(即非堆内存"无上限")时返回-1,否则返回堆与非堆 max 之和,避免出现错误的求和结果。
堆层(heap):
heap.init、heap.used、heap.max、heap.committedheap.usage:一个RatioGauge,比值 =used / max
非堆层(non-heap):
non-heap.init、non-heap.used、non-heap.max、non-heap.committednon-heap.usage:比值 =used / (max == -1 ? committed : max)
从源码可见,当max为-1时usage会退化为以committed作为分母——这是处理 Metaspace 这类"理论无上限"内存区域的必要容错。
内存池层(pools),遍历每个MemoryPoolMXBean:
pools.<pool>.usage、pools.<pool>.max、pools.<pool>.used、pools.<pool>.committed、pools.<pool>.initpools.<pool>.used-after-gc:仅当该池支持 GC 后使用量统计(pool.getCollectionUsage() != null)时才注册
例如 HotSpot 上典型的内存池指标会形如pools.PS-Eden-Space.used、pools.PS-Old-Gen.usage、pools.Code-Cache.committed、pools.Metaspace.used-after-gc。由于pools.<pool>覆盖了堆内存池(Eden、Survivor、Old 等)和堆外内存池(Code Cache、Metaspace 等),因此手册所说的"包括 off-heap 内存"正是由这一层级完整承载的。
线程指标与死锁检测:ThreadStatesGaugeSet
ThreadStatesGaugeSet.java 的数据来源是ThreadMXBean。它注册以下指标:
- 每个线程状态一个计数:
new.count、runnable.count、blocked.count、waiting.count、timed_waiting.count、terminated.count(对应Thread.State枚举的全部值,名称由state.toString().toLowerCase()生成); count:当前线程总数(getThreadCount());daemon.count:守护线程数(getDaemonThreadCount());peak.count:历史峰值线程数(getPeakThreadCount());total_started.count:累计启动线程总数(getTotalStartedThreadCount());deadlock.count:死锁线程数;deadlocks:死锁线程的诊断信息集合。
在内部实现上,getThreadCount(state)通过threads.getThreadInfo(threads.getAllThreadIds(), STACK_TRACE_DEPTH)一次性取回所有线程的ThreadInfo(STACK_TRACE_DEPTH = 0表示不计算栈帧,降低开销),再逐个比对状态。从源码结构看,每个状态 gauge 被读取时都会触发一次全量线程信息快照,对于线程数巨大的应用这会带来可观的 CPU 开销。
为此模块提供了变体 CachedThreadStatesGaugeSet.java:它继承ThreadStatesGaugeSet,把ThreadInfo[]缓存进一个CachedGauge,在给定的时间间隔内只刷新一次:
// 每 5 秒缓存一次线程信息,避免高频快照开销 registry.registerAll(new CachedThreadStatesGaugeSet(5, TimeUnit.SECONDS));死锁检测由独立的 ThreadDeadlockDetector.java 完成:它调用ThreadMXBean.findDeadlockedThreads()找出死锁线程,然后以MAX_STACK_TRACE_DEPTH = 100的深度抓取每个死锁线程的栈帧,输出形如线程名 locked on 锁 (owned by 持有者)的多行诊断字符串。没有死锁时返回空集合,因此deadlock.count为 0、deadlocks为空,是健康基线。
文件描述符指标:FileDescriptorRatioGauge
FileDescriptorRatioGauge.java 是一个RatioGauge,返回"已打开文件描述符 / 最大文件描述符"的比值,是排查"Too many open files"类故障的经典监控项。
这里有一个重要的平台限制:文件描述符计数依赖com.sun.management.UnixOperatingSystemMXBean。源码在静态初始化块中通过Class.forName("com.sun.management.UnixOperatingSystemMXBean")探测该类是否存在,运行时若当前OperatingSystemMXBean是其实例(典型 Linux/macOS JVM 满足),则返回Ratio.of(getOpenFileDescriptorCount(), getMaxFileDescriptorCount());否则返回Ratio.of(NaN, NaN)。因此在非 Unix 平台或探测失败时,该 gauge 会产出 NaN 而不是真实数值,接入告警时需要注意这一点。对应测试见 FileDescriptorRatioGaugeTest.java。
缓冲池指标:BufferPoolMetricSet
BufferPoolMetricSet.java 采集 JVM 的直接(direct)与映射(mapped)缓冲池,指标形如:
direct.count、direct.used、direct.capacitymapped.count、mapped.used、mapped.capacity
它直接读取java.nio:type=BufferPool,name=direct与java.nio:type=BufferPool,name=mapped两个 JMX MBean 的Count、MemoryUsed、TotalCapacity属性(通过模块内的 JmxAttributeGauge.java 封装成 gauge)。注意它的构造器需要显式传入MBeanServer(通常用ManagementFactory.getPlatformMBeanServer())。源码注释明确指出这类 JMX 对象只在 Java 7 及以上可用;若 MBean 不存在(如在 Java 6 上运行),代码会捕获JMException并记录 debug 日志后跳过对应指标,因此注册它是安全无副作用的。
其他内置 MetricSet
除了手册列出的五大类,模块还附带两个轻量指标集:
- ClassLoadingGaugeSet.java:注册
loaded(累计加载类数)与unloaded(卸载类数)两个 gauge,数据来自ClassLoadingMXBean,可用于观察类加载泄漏(ClassLoader 无法被回收)。 - JvmAttributeGaugeSet.java:注册
name(JVM 标识)、vendor(厂商/版本/规格拼接串)、uptime(进程运行毫秒数),数据来自RuntimeMXBean。通常配合CpuTimeClock使用,以标记主机信息与进程生命周期。
另外,CpuTimeClock.java 提供了基于ThreadMXBean.getCurrentThreadCpuTime()的Clock实现,如果你希望 Timer 的计时基于线程 CPU 时间而非墙钟时间,可以把Clock传入 registry 或 timer 构造器。
指标命名与源码约定
从上述源码可以总结出几条可复用的命名约定,方便在监控系统中检索:
- 所有 GC、内存池名称中的空白都会被替换为
-(如PS Scavenge→PS-Scavenge); - 嵌套命名使用
.分隔(pools.PS-Eden-Space.used、heap.usage),与MetricRegistry.name()的点分风格一致; - 属性类指标以量词收尾:
count、time、used、max、committed、init、capacity、uptime; - 比例类指标用
usage(heap.usage、non-heap.usage、pools.<pool>.usage)或直接以 RatioGauge 形式注册。
验证与测试
每个指标集在 metrics-jvm 测试目录 下都有对应测试:GarbageCollectorMetricSetTest、MemoryUsageGaugeSetTest、ThreadStatesGaugeSetTest、BufferPoolMetricSetTest、ClassLoadingGaugeSetTest、FileDescriptorRatioGaugeTest、ThreadDeadlockDetectorTest等。它们大量通过 Mockito 注入 mock 的 MXBean,验证getMetrics()返回的指标名与取值映射关系。阅读这些测试是理解每个指标精确语义的最快途径,例如FileDescriptorRatioGaugeSunManagementNotExistsTest就专门验证了"UnixOperatingSystemMXBean 不可用时返回 NaN"这条平台降级路径。
结合 Reporter 与 Servlet 形成完整方案
metrics-jvm只负责采集,输出交给metrics-core的 Reporters 与 Servlets。推荐组合:
- 用
CsvReporter落盘 JVM 指标做历史分析; - 用
Slf4jReporter定期输出到日志; - 用 MetricsServlet 把 registry 中全部 JVM 指标以 JSON 暴露给前端监控面板;
- 用 ThreadDumpServlet(内部复用模块的 ThreadDump.java)在排障时抓取完整线程转储。
若想在生产环境通过 JMX 在线浏览这些指标,可以使用 JDK 自带的jconsole或jvisualvm的 MBeans 插件查看JmxReporter暴露的 MBean——参见手册中 JMX Reporter 一节。
小结
metrics-jvm用极少的接入成本补齐了 JVM 观测的关键拼图:GC、堆/堆外内存、线程状态与死锁、文件描述符、缓冲池,全部以标准MetricSet形式与metrics-core无缝融合。使用时只需记住三点:用registerAll批量注册、注意FileDescriptorRatioGauge的 Unix 平台限制与BufferPoolMetricSet的 Java 7+ 要求、线程数巨大的应用优先改用CachedThreadStatesGaugeSet控制采样开销。基于这些指标,配合任意 reporter 即可快速构建出一套覆盖 JVM 核心运行面的监控体系。
- 可观测性
- 后端
【免费下载链接】metrics
:chart_with_upwards_trend: Capturing JVM- and application-level metrics. So you know what's going on.
相关推荐
使用 Dropwizard Metrics 采集 Caffeine 缓存指标:MetricsStatsCounter 实战指南
使用 Dropwizard Metrics 采集 Caffeine 缓存指标:MetricsStatsCounter 实战指南 本文以 Metrics 项目的
可观测性后端AMD MI350 vs NVIDIA A100:Qwen3.5-397B-A17B-NVFP4跨平台推理性能对比终极指南
AMD MI350 vs NVIDIA A100:Qwen3.5 397B A17B NVFP4跨平台推理性能对比终极指南 在当今大模型推理领域,硬件选择直接影
大模型基础模型模型量化多模态本地部署go-metrics 指标采集指南:基于 Prometheus 的 Docker 项目指标命名规范与实战(buildkit 仓库内嵌版解析)
go metrics 指标采集指南:基于 Prometheus 的 Docker 项目指标命名规范与实战(buildkit 仓库内嵌版解析) 本篇技术指南围绕当
构建工具云原生后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考