- 运维观测
- 系统编程
【免费下载链接】oshi
Native Operating System and Hardware Information
OSHI(Native Operating System and Hardware Information)是 Java 生态中获取操作系统与硬件信息的跨平台库,同时提供 JNA(oshi-core)与 FFM(oshi-core-ffm)两套原生访问实现。本文以仓库根目录下的 PERFORMANCE.md 为主线,系统讲解两套实现的基准差异、SystemInfo对象的复用与重建取舍、列表对象增量更新策略,以及 Windows 性能计数器、命令行批查询、COM 初始化与 Linux NFS 探测等专项调优手段——读完你将掌握如何用 JMH 复现基准数据,并根据自己的监控场景正确配置 OSHI,避免把毫秒级开销放大成轮询瓶颈。
一、FFM 与 JNA:两套原生访问实现的性能全景
OSHI 提供两套原生访问实现,二者 API 一致但底层机制不同:
- JNA(
oshi-core):支持 JDK 8+ 以及 OSHI 覆盖的全部平台。JNA 基于反射进行原生调用的参数封装(marshalling),每次调用都存在额外开销。 - FFM(
oshi-core-ffm):要求 JDK 25+,支持与 JNA 实现相同的平台。FFM(Foreign Function & Memory API)使用编译器优化的桩(stub)执行原生调用,显著降低单次调用开销。FFM 的主入口是oshi.ffm.SystemInfo,源码见 oshi-core-ffm/src/main/java/oshi/ffm/SystemInfo.java。
下文各平台数据来自 JMH 基准(5 次预热迭代、7 次测量迭代、5 个 fork)的近似平均值,由手动触发的Benchmarks (manual)工作流(.github/workflows/benchmarks.yaml)在 GitHub Actions 运行器上采集。该工作流仅以workflow_dispatch方式触发,避免在每次 push/PR 时消耗 CI 时间;新增平台或做出影响性能的改动后应手动重跑。
macOS:FFM 收益最大的平台
| Benchmark | FFM | JNA |
|---|---|---|
| CpuTicks | ~5 µs | ~6 µs |
| FileStore | ~43 µs | ~97 µs |
| Memory | ~3 µs | ~7 µs |
| NetworkIF | ~18 µs | ~115 µs |
| Processes | ~1.5 ms | ~10.7 ms |
macOS 上 FFM 优势最大,因为多数系统信息依赖原生系统调用(sysctl、proc_pidinfo等),FFM 更低的单次调用开销在大量调用中不断累积放大。
Windows
| Benchmark | FFM | JNA |
|---|---|---|
| CpuTicks | ~0.24 ms | ~0.46 ms |
| FileStore | ~32 µs | ~117 µs |
| Memory | ~72 µs | ~111 µs |
| NetworkIF | ~4.0 ms | ~1.1 ms |
| Processes | ~8.2 ms | ~8.8 ms |
Windows 在多数基准上受益于 FFM。NetworkIF 的反常现象(FFM 更慢)源于 CI 运行器上偶发的 WMI 查询延迟尖峰。
Linux:差异最小的平台
| Benchmark | FFM | JNA |
|---|---|---|
| CpuTicks | ~23 µs | ~24 µs |
| FileStore | ~4 µs | ~30 µs |
| Memory | ~83 µs | ~82 µs |
| NetworkIF | ~949 µs | ~951 µs |
| Processes | ~10.6 ms | ~10.9 ms |
Linux 上两套实现差异最小:Linux 将多数系统信息暴露在/proc与/sys伪文件系统中,OSHI 直接按普通文件读取、无需原生调用。例外是 FileStore——FFM 直接调用statvfs,绕开了 JNA 的 marshalling 开销。
FreeBSD / NetBSD / OpenBSD / Solaris / AIX
FreeBSD、NetBSD、OpenBSD 与 Solaris(illumos)均在 Ubuntu 运行器上的嵌套 QEMU 虚拟机中执行,绝对值噪声较大,只有单次运行内 JNA 与 FFM 的相对比较才有意义:
- FreeBSD:几乎无 FFM/JNA 差距,多数数据来自
sysctl读取,不受单次调用开销制约。 - NetBSD:JNA 列依赖 pkgsrc 的
java-jna提供原生libjnidispatch,否则 JNA 实现会回退到 native-free provider 使用的命令行路径。只有CpuTicks(每个处理器一次kern.cp_timesysctl)被原生调用主导,FFM 更低的单次调用开销在此显现。 - OpenBSD:
FileStore与Processes数值在嵌套 VM 中误差极大,仅作数量级参考。 - Solaris (illumos):FFM 在
CpuTicks上优势最悬殊(约快 80 倍)——每次 CPU tick 读取都跨越libkstat/kstat2边界,JNA 的单次 marshalling 成本主导了工作量。 - AIX:
CpuTicks同样有约 80 倍的 FFM 优势,每次读取都穿过一次libperfstat调用;FileStore受限于文件系统stat()调用,对原生绑定开销不敏感。AIX 在 cfarm119(共享真实硬件,通过 SSH 访问,OpenJ9 JDK 25)上运行,数值比嵌套 VM 平台稳定,但比专用 GitHub Actions 运行器噪声大。
本地运行基准
上述数值来自 GitHub Actions CI 运行器,可能与你的硬件和工作负载(运行中的进程数、挂载的文件存储、网络接口)差异很大。oshi-benchmark模块包含的 JMH 基准可用 JDK 25+ 构建运行:
./mvnw package -pl oshi-benchmark -am -DskipTests -Dshade.phase=package -q java -jar oshi-benchmark/target/benchmarks.jar -wi 5 -i 7 -f 5运行java -jar oshi-benchmark/target/benchmarks.jar -h可查看全部 JMH 选项,包括列出可用基准(-l)以及用正则过滤器运行指定基准。
从源码看,每个基准类都通过GlobalConfig.set(GlobalConfig.OSHI_UTIL_MEMOIZER_EXPIRATION, 0)在@Setup中禁用记忆化(memoizer),再分别实例化 JNA 与 FFM 两套SystemInfo做对照。例如 CpuTicksBenchmark.java 分别用oshi.SystemInfo与oshi.ffm.SystemInfo初始化两个CentralProcessor,并在@Fork中注入--enable-native-access=ALL-UNNAMED。oshi-benchmark的 pom(oshi-benchmark/pom.xml)通过 shade 插件将 JMH 打成benchmarks.jar超级 jar,主类为org.openjdk.jmh.Main。仓库还提供了便捷脚本 run-benchmarks.sh,它会自动定位仓库根、校验 JDK 25+、按需构建 jar 并把参数直接透传给 JMH:
./oshi-benchmark/scripts/run-benchmarks.sh CpuTicksBenchmark -wi 2 -i 10 -f 1可用基准一览(见 oshi-benchmark/README.md):CpuTicksBenchmark(getSystemCpuLoadTicks())、MemoryBenchmark(getTotal()/getAvailable())、FileStoreBenchmark(getFileStores())、NetworkIFBenchmark(getNetworkIFs())、ProcessesBenchmark(getProcesses())以及ReuseVsRecreateBenchmark(复用与重建SystemInfo的取舍)。
二、CPU/内存权衡:缓存策略与SystemInfo复用
OSHI 本身不做大量信息缓存,把缓存决策留给使用者;多个类以实例字段中的 Memoized supplier 做有限度缓存(TTL 默认 300 毫秒,可调),从而避免重复系统调用。其底层实现见 Memoizer.java:memoize返回一个线程安全的过期型 supplier,ttlNanos为负则永久保留,TTL 由配置键oshi.util.memoizer.expiration(毫秒)控制,对应常量GlobalConfig.OSHI_UTIL_MEMOIZER_EXPIRATION,见 GlobalConfig.java。
内存受限的用户可以在采集数据后丢弃SystemInfo及其下属类实例来释放已缓存信息。
轮询场景:复用还是重建SystemInfo
周期性采样同一批指标的监控代理应持有单个SystemInfo及其下属对象,而不是每次轮询都新建一个。真实轮询间隔远长于亚秒级 memoizer TTL,因此 memoizer 无法跨轮询生效;真正的取舍是"每次轮询重建对象图的 CPU 开销"与"持有对象图占用的内存"。
重建的主要成本集中在CentralProcessor(CPU 拓扑与标识查询)。由ReuseVsRecreateBenchmark(禁用 memoization,-f 3)测得的读取 CPU 负载 tick 的单次成本如下,源码见 ReuseVsRecreateBenchmark.java:
| Platform | Recreate per poll | Reuse (held) per poll | Retained if held |
|---|---|---|---|
| Windows | 10.7 ms | 632 µs | 3.9 KB |
| macOS | 3.8 ms | 2.4 µs | 8.5 KB |
| Linux | 2.4 ms | 19.5 µs | 5.0 KB |
每次重建在毫秒级(Windows 上最贵,因为 CPU 标识来自 WMI/注册表),换来的是节省几 KB 的常驻内存——对 CPU 派生指标是明显划算的交易。该基准通过@Param({ "cpuTicks", "memAvailable", "processes" })分别测量三种指标,reuse()与recreate()两个@Benchmark方法对应两种策略。
两条重要的反例:
- 可用内存不受益:各平台
GlobalMemory构造都很廉价,recreate ≈ reuse,该优势仅对CentralProcessor成立。 - 进程列表不受益:
getProcesses()是实时全量查询(各平台每轮约 10 ms 且分配 1–15 MB,复用与否都一样)。要跟踪特定进程,应持有单个OSProcess对象并调用updateAttributes(),而不是反复查询整个列表(详见下文)。
持有单个SystemInfo以及它返回的每个子系统的一份快照(由MonitoringFootprintReport测量)约保留0.65 MB,其中进程列表快照占大头;仅常驻的硬件/OS 缓存合计不到 ~150 KB。内存受限的应用可以丢弃SystemInfo来释放这些内存,代价是承担上述重建开销。MonitoringFootprintReport是一个普通main程序(非 JMH 基准,因为常驻占用不是单次操作量),通过堆差值法(持有 N 份拷贝、强制 GC、取差值)估算常驻大小,运行方式见 MonitoringFootprintReport.java:
java --enable-native-access=ALL-UNNAMED -cp oshi-benchmark/target/benchmarks.jar oshi.benchmark.MonitoringFootprintReport三、列表对象统计的增量更新策略
OSProcess、NetworkIF、OSFileStore等列表返回的个体对象大都提供updateAttributes()方法,只作用于该对象本身,适合仅监控单个进程的场景。但很多情况下必须查询整个列表才能拿到信息,因此要同时更新列表中多个对象时,正确做法是整体重新查询一次列表,再在自己的应用里把新结果与旧结果做关联,而不是逐个对象反复调用updateAttributes()。
四、Windows 专项调优
性能计数器:默认优先注册表
出于性能考虑,OSHI 默认从注册表基键HKEY_PERFORMANCE_DATA读取进程与线程信息,而非性能计数器。该方案可能与本地化环境冲突,可通过配置文件或启动后立即调用(至少在查询进程列表前)禁用:
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_HKEYPERFDATA, false);对应配置键为oshi.os.windows.hkeyperfdata(默认true),见 GlobalConfig.java。
部分用户(如游戏场景)会为性能考虑在注册表中禁用性能计数器,此时 OSHI 检测到禁用计数器时打出的日志警告可能造成困惑。如果你的应用不使用这些类别的性能计数器信息,可通过以下配置抑制警告(默认均未设置,OSHI 从注册表自动检测,对应键见 GlobalConfig.java):
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_PERFOS_DISABLED, true); // oshi.os.windows.perfos.disabled GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_PERFPROC_DISABLED, true); // oshi.os.windows.perfproc.disabled GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_PERFDISK_DISABLED, true); // oshi.os.windows.perfdisk.disabled进程命令行:按需单查或批量预取
Windows 上进程命令行只在 WMI 中可用,除非 OSHI 以提升权限运行或进程属于同一用户,否则查询要付出显著的 WMI 开销。
默认情况下OSProcess不预取命令行,按每次查询逐进程单独发一次查询(适合用户临时按需查询)。如果应用需要更新多个属于其他用户的命令行,一次性获取并缓存整份命令行结果可显著改善 CPU 性能,需在配置文件中启用或启动后、第一次命令行查询前调用:
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_COMMANDLINE_BATCH, true); // oshi.os.windows.commandline.batch对应实现见 WindowsOSProcessJNA.java:开启后getCommandLine()走Win32ProcessCachedJNA.getInstance().getCommandLine(...)的批量缓存路径,否则走单进程的 WMI 查询。
挂起进程检测
Windows 上判断进程是否挂起需要查询其线程状态。默认所有 Windows 进程都显示为 "Running"。如果应用需要知道哪些进程被挂起,按需(ad hoc)收集线程详情效果最好;若要查询全部进程,则查询线程性能计数器更高效(但仍较慢)。需在启动后、首次实例化 Operating System 类前启用:
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_PROCSTATE_SUSPENDED, true); // oshi.os.windows.procstate.suspended对应键见 GlobalConfig.java,启用后WindowsOSProcessJNA会为每个 PID 查询匹配的线程性能计数器块。
WMI 查询的 COM 初始化
OSHI 不知道外部是否已初始化 COM,因此每次 WMI 查询都要做一次 COM 的初始化与反初始化。如果你的应用自行完成 COM 初始化,并让 OSHI 使用一个省略该开销的WmiQueryHandler子类,每次 WMI 查询的响应时间可缩短约 3–5 毫秒。oshi-demo构件中的UserComInit与WmiNoComInitQueryHandler演示了这一能力,可在此基础上继续定制。
从源码看,核心在 WmiQueryHandler.java:queryWMI(query, boolean initCom)在initCom=true时先initCOM()再执行查询、finally中unInitCOM();工厂方法createInstance()会实例化由setInstanceClass(Class)注册的子类。WmiNoComInitQueryHandler覆写initCOM()直接返回false,跳过 COM 初始化,见 WmiNoComInitQueryHandler.java。演示类 UserComInit.java 展示了完整流程:先用默认 handler 做一次 COM 初始化(initCOM()),再setInstanceClass(WmiNoComInitQueryHandler.class)切换查询处理器,最后在finally中反初始化 COM。
五、物理设备延迟与 Linux NFS 探测
磁盘、文件存储、USB 设备及部分网络信息的统计更新会引入物理设备或网络延迟,比 CPU、内存这类基于 OS 内核的统计响应更慢。周期性轮询这类数据的频率应低于内核态统计;若只需要不变的磁盘统计,禁用 PerfDisk 计数器查询可小幅提升性能。
Linux 上getFileStores()对每个挂载点调用statvfs(),而hard方式挂载的 NFS 文件系统在服务器不可达时可能无限阻塞(默认挂载选项没有超时)。为避免卡死,OSHI 在调用statvfs()之前会并行探测各 NFS 服务器的可达性(TCP 2049 端口,2 秒超时),对不可达的挂载返回零值文件存储而不是挂起。该探测对可达的 NFS 挂载只增加一次短暂的 TCP 连接,不影响本地文件系统或localOnly查询。如果由你在外部处理 NFS 超时,可用以下配置关闭探测:
GlobalConfig.set(GlobalConfig.OSHI_OS_LINUX_FILESYSTEM_CHECKNFS, false); // oshi.os.linux.filesystem.checknfs实现细节见 LinuxFileSystem.java:probeNfsHosts先从/proc/mounts解析所有nfs/nfs4挂载的addr=/mountaddr=地址,用上限 64 线程的固定线程池并行执行tcpReachable(host, 2049, 2000);不可达主机对应挂载直接返回 "Network Disk [unreachable]" 的零值LinuxOSFileStore,跳过statvfs。localOnly扫描因已过滤网络文件系统而完全跳过探测(LinuxFileSystem.java)。值得注意,探测使用 TCP connect 而非InetAddress.isReachable()(ICMP 可能需要 root 权限)或 7 端口 echo(常被防火墙拦截),并专门限定nfs/nfs4类型,避免对 cifs、fuse 等非 NFS 网络文件系统做 2049 端口探测。
六、配置加载与调优速查
所有上述调优键均通过GlobalConfig统一读取。配置按以下优先级解析(高者优先,见 GlobalConfig.java):
- Java 系统属性(
-Doshi.*) - 环境变量(
OSHI_*,通过大写并替换点为下划线映射) - 外部属性文件(
-Doshi.properties.file=/path/to/file) - classpath 上的
oshi.properties(默认值)
汇总本文涉及的配置键及其默认值:
| 配置键 | 常量 | 默认值 | 作用 |
|---|---|---|---|
oshi.util.memoizer.expiration | OSHI_UTIL_MEMOIZER_EXPIRATION | 300(毫秒) | memoized 值刷新间隔 |
oshi.os.windows.hkeyperfdata | OSHI_OS_WINDOWS_HKEYPERFDATA | true | 进程/线程信息优先读注册表而非性能计数器 |
oshi.os.windows.perfos.disabled | OSHI_OS_WINDOWS_PERFOS_DISABLED | 未设置(自动检测) | 抑制 PerfOS 计数器禁用告警 |
oshi.os.windows.perfproc.disabled | OSHI_OS_WINDOWS_PERFPROC_DISABLED | 未设置(自动检测) | 抑制 PerfProc 计数器禁用告警 |
oshi.os.windows.perfdisk.disabled | OSHI_OS_WINDOWS_PERFDISK_DISABLED | 未设置(自动检测) | 抑制 PerfDisk 计数器禁用告警 |
oshi.os.windows.commandline.batch | OSHI_OS_WINDOWS_COMMANDLINE_BATCH | false | 批量预取全部进程命令行 |
oshi.os.windows.procstate.suspended | OSHI_OS_WINDOWS_PROCSTATE_SUSPENDED | false | 通过线程状态检测挂起进程 |
oshi.os.linux.filesystem.checknfs | OSHI_OS_LINUX_FILESYSTEM_CHECKNFS | true | statvfs 前并行探测 NFS 可达性 |
七、性能调优决策要点小结
- 选 FFM 还是 JNA:能使用 JDK 25+ 且需要更低原生调用开销时优先 FFM;需要支持 JDK 8+ 则用 JNA。FFM 在 macOS、Solaris/AIX 的
CpuTicks等原生调用密集场景收益最大,Linux 上差异有限。 - 监控轮询:重复采样同一指标应持有单个
SystemInfo;仅当内存极度受限且可承受毫秒级重建成本时才考虑每轮新建。 - 进程跟踪:跟踪少量进程用
updateAttributes(),需要整表则一次查询整表再自行关联。 - Windows 专属开关:按需开启
commandline.batch、procstate.suspended;自行初始化 COM 可省去每次 WMI 查询 3–5 ms 开销;游戏机或禁用计数器环境下用perf*.disabled消除告警。 - Linux NFS:保留默认的
checknfs探测可防止statvfs在不可达 NFS 上无限阻塞;由外部处理超时时再关闭。
- 运维观测
- 系统编程
【免费下载链接】oshi
Native Operating System and Hardware Information
相关推荐
Oemer简介:端到端光学音乐识别(OMR)完整指南,AI一张图把乐谱变MusicXML
Oemer简介:端到端光学音乐识别 OMR 完整指南,AI一张图把乐谱变MusicXML Oemer 是一个端到端的 光学音乐识别(OMR, Optical M
Netdata性能基准终极指南:资源消耗对比与优化策略
Netdata性能基准终极指南:资源消耗对比与优化策略 Netdata作为一款实时性能监控工具,其资源消耗表现直接影响着部署决策和使用体验。本文通过详尽的基准测
可观测性指标监控告警异常检测PCL点云特征提取终极教程:PFH、FPFH与NARF特征实战
PCL点云特征提取终极教程:PFH、FPFH与NARF特征实战 在三维点云处理领域, PCL点云特征提取 是连接原始点云数据与高级应用的关键桥梁。本文为您带来
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考