news 2026/10/13 2:52:40

OSHI 性能优化实战指南:FFM/JNA 基准对比、轮询策略与 Windows/Linux 专项调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OSHI 性能优化实战指南:FFM/JNA 基准对比、轮询策略与 Windows/Linux 专项调优
  • 运维观测
  • 系统编程

【免费下载链接】oshi

Native Operating System and Hardware Information

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

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 收益最大的平台

BenchmarkFFMJNA
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

BenchmarkFFMJNA
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:差异最小的平台

BenchmarkFFMJNA
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:

PlatformRecreate per pollReuse (held) per pollRetained if held
Windows10.7 ms632 µs3.9 KB
macOS3.8 ms2.4 µs8.5 KB
Linux2.4 ms19.5 µs5.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):

  1. Java 系统属性(-Doshi.*)
  2. 环境变量(OSHI_*,通过大写并替换点为下划线映射)
  3. 外部属性文件(-Doshi.properties.file=/path/to/file)
  4. classpath 上的oshi.properties(默认值)

汇总本文涉及的配置键及其默认值:

配置键常量默认值作用
oshi.util.memoizer.expirationOSHI_UTIL_MEMOIZER_EXPIRATION300(毫秒)memoized 值刷新间隔
oshi.os.windows.hkeyperfdataOSHI_OS_WINDOWS_HKEYPERFDATAtrue进程/线程信息优先读注册表而非性能计数器
oshi.os.windows.perfos.disabledOSHI_OS_WINDOWS_PERFOS_DISABLED未设置(自动检测)抑制 PerfOS 计数器禁用告警
oshi.os.windows.perfproc.disabledOSHI_OS_WINDOWS_PERFPROC_DISABLED未设置(自动检测)抑制 PerfProc 计数器禁用告警
oshi.os.windows.perfdisk.disabledOSHI_OS_WINDOWS_PERFDISK_DISABLED未设置(自动检测)抑制 PerfDisk 计数器禁用告警
oshi.os.windows.commandline.batchOSHI_OS_WINDOWS_COMMANDLINE_BATCHfalse批量预取全部进程命令行
oshi.os.windows.procstate.suspendedOSHI_OS_WINDOWS_PROCSTATE_SUSPENDEDfalse通过线程状态检测挂起进程
oshi.os.linux.filesystem.checknfsOSHI_OS_LINUX_FILESYSTEM_CHECKNFStruestatvfs 前并行探测 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

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

相关推荐

上一篇:如何在404时代拯救你心爱的小说?novel-downloader小说下载器终极指南
下一篇:5分钟搞定!Windows电脑直接运行安卓应用的神器APK安装器终极指南

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

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

数字人源码包实战指南:从环境搭建到口型驱动参数调优

简介:面向数字人技术研究者与小程序开发者,这是一份基于微信小程序实现的数字人源码包,涵盖数字人界面展示、交互逻辑与视觉资源,可帮助读者从零理解数字人在移动端的搭建思路,也便于后续二次开发。包内共129个文件&am…

作者头像 李华
网站建设 2026/10/13 2:52:06

技术人跨界农业5年:养殖数字化与物联网实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 2:51:10

恶意网站检测实战:机器学习与URL特征工程全流程解析

简介:面向网络安全与机器学习初学者的恶意网站检测实践资源,基于传统机器学习与深度学习方法,利用黑名单/白名单数据完成特征提取、可视化与分类建模。资源提供完整可运行的 Python 代码,覆盖特征提取、数据可视化,以及…

作者头像 李华
网站建设 2026/10/13 2:49:39

SA-SSD复现避坑指南:辅助网络、Loss计算与数据增强全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华