news 2026/9/27 7:38:26

Metrics JVM Instrumentation 实战指南:基于 Dropwizard Metrics 采集 GC、内存、线程与文件描述符指标

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Metrics JVM Instrumentation 实战指南:基于 Dropwizard Metrics 采集 GC、内存、线程与文件描述符指标
  • 可观测性
  • 后端

【免费下载链接】metrics

:chart_with_upwards_trend: Capturing JVM- and application-level metrics. So you know what's going on.

项目地址:https://gitcode.com/gh_mirrors/met/metrics
点击查看免费下载

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.committed
  • heap.usage:一个RatioGauge,比值 =used / max

非堆层(non-heap):

  • non-heap.init、non-heap.used、non-heap.max、non-heap.committed
  • non-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>.init
  • pools.<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.capacity
  • mapped.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。推荐组合:

  1. 用CsvReporter落盘 JVM 指标做历史分析;
  2. 用Slf4jReporter定期输出到日志;
  3. 用 MetricsServlet 把 registry 中全部 JVM 指标以 JSON 暴露给前端监控面板;
  4. 用 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.

项目地址:https://gitcode.com/gh_mirrors/met/metrics
点击查看免费下载

相关推荐

上一篇:【亲测免费】 WeUI for 小程序安装与配置指南
下一篇:零基础掌握Presto:从安装到配置的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 7:36:21

MySQL用户与权限管理

MySQL 是一种广泛使用的关系型数据库管理系统,支持多用户访问和权限控制。在多用户环境下,数据库安全至关重要,而用户和权限管理是数据库管理中最基础也是最重要的一部分。通过合理地创建和管理用户、分配和管理权限、使用角色权限,可以有效地保护数据库,确保数据的安全性…

作者头像 李华
网站建设 2026/9/27 7:27:02

Jev 能否用于银行风控?从规则引擎到语义决策层

在银行和消费金融领域&#xff0c;Blaze、Drools 等规则引擎已经使用多年。无论贷前授信、贷中风险监控&#xff0c;还是贷后逾期管理&#xff0c;本质上都存在大量“根据客户状态进行判断&#xff0c;再选择下一步策略”的业务逻辑。例如贷后催收系统通常会根据逾期天数、逾期…

作者头像 李华