⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Fight/SpringBoot-3-jicheng-Hutool-OshiUtilyixingdaimagaoding-CPUneicuncipanjiankong/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

它不是「让 Java 拿到系统信息」,是「让你不直接用 OSHI」

后台需求里总会出现这种场景:监控页面要显示服务器 CPU/内存/磁盘,启动日志要打印 JVM 版本,health check 要带几个系统指标。这事最早大家都用 System.getProperty() 硬拼——能拿到 OS、Java 版本,但拿不到 CPU 实时使用率。

后来大家都迁去 OSHI(Operating System and Hardware Information)。它通过 JNA 调本地 API,跨平台、能拿全套硬件指标——但API 复杂,每次用都得翻文档,监控代码越写越长。

这次推荐的是 Hutool 工具库 里的 OshiUtil——它不是另一个监控库,而是 OSHI 的 Hutool 封装层:

  • OSHI 复杂的查询 API 被压成静态方法 OshiUtil.getCpuInfo()、OshiUtil.getMemory() 这种一目了然的调用;
  • 90% 的监控场景根本不需要 OSHI 的全部能力,OshiUtil 只露出常用接口、屏蔽剩下的;
  • 在已经用了 Hutool 的项目里,不用再多引一个工具库,等于零成本接入。

简单说:OshiUtil 不是「替代 OSHI」的工具,是「让 90% 的人不需要直接接 OSHI」的捷径。

横向对比:4 个方案怎么选

不是所有项目都该上 OshiUtil。先看自己在哪个象限:

方案 能拿到啥 优点 致命短板
System.getProperty() OS 名 / Java 版本 / 用户目录 零依赖 拿不到 CPU 使用率、内存、磁盘
Runtime.getRuntime() JVM 内存 + 处理器数 零依赖 没有 OS 层信息(系统负载、磁盘)
OshiUtil(推荐) CPU/内存/磁盘/网络/JVM 一行调用、API 简洁 不暴露 OSHI 全部高级能力
原生 OSHI 全部,含进程级 / IO 级 最完整 API 复杂、要翻文档
Spring Boot Actuator health / metrics 端点 标准 / 自动 JSON 出口固定,不适合自定义页面

OshiUtil 能拿到的信息全集——CPU 核心数 / 实时使用率、内存总量与可用、操作系统名与架构、JVM 运行时、磁盘容量、网络接口、MAC 地址。

判断推荐很简单:

  • 只要 JVM 信息 + OS 名:用 Runtime + System.getProperty() 就够,不上工具库;
  • 要 CPU 实时使用率 / 磁盘 / 网卡,但不深入到进程级:OshiUtil 是性价比最高的选择;
  • 要 进程监控 / 磁盘 IO / 文件系统级:直接用原生 OSHI,OshiUtil 这一层会挡住你;
  • 只是给 Prometheus 暴露指标:上 Actuator + Micrometer,不要重复造轮子。

3 个真实场景:什么时候用得上

场景 1:应用启动时打印系统快照

线上 K8s 集群里同一个镜像跑在不同 Node 上,出问题时第一件事是「我现在到底在哪台机器上、配置如何」。启动日志带一段系统信息能省半小时排查:

@Component
public class SystemInfoLogger implements CommandLineRunner {

@Override
public void run(String... args) {
log.info("系统 : {}", OshiUtil.getOs());
log.info("JVM : {}", SystemUtil.getJavaInfo().getVersion());
log.info("CPU : {} 核", OshiUtil.getCpuInfo().getCpuNum());
log.info("内存 : {} GB", OshiUtil.getMemory().getTotal() / 1024 / 1024 / 1024);
}
}

痛点:拿到 Pod 后再 kubectl exec 进容器查环境很慢。
收益:启动日志 + ELK 关键词检索秒定位,故障定位提前到「拿日志」这一步。

场景 2:自定义 health 接口

Spring Boot Actuator 的 /actuator/health 输出是死的——前端管理后台想要中文 + 自定义字段时,自己写一个反而更轻量:

@GetMapping("/system/health")
public Map<String, Object> health() {
return Map.of(
"status", "UP",
"cpuUsedPercent", OshiUtil.getCpuInfo().getUsed(),
"memAvailableMB", OshiUtil.getMemory().getAvailable() / 1024 / 1024,
"os", OshiUtil.getOs().toString()
);
}

痛点:Actuator 端点格式固定,前端要适配。
收益:5 行写一个自定义 JSON 接口,管理后台不再嵌 Actuator 解析逻辑。

场景 3:管理后台「服务器状态」页

中后台系统几乎都有这种「运维概览」页面:

封装一个 Service 给前端:

@Service
public class SystemMonitorService {

public Map<String, Object> getSystemInfo() {
Map<String, Object> r = new HashMap<>();
CpuInfo cpu = OshiUtil.getCpuInfo();
r.put("cpuCores", cpu.getCpuNum());
r.put("cpuUsed", String.format("%.2f%%", cpu.getUsed()));

GlobalMemory mem = OshiUtil.getMemory();
r.put("memTotalGB", String.format("%.2f", mem.getTotal() / 1024.0 / 1024 / 1024));
r.put("memFreeGB", String.format("%.2f", mem.getAvailable() / 1024.0 / 1024 / 1024));

r.put("os", OshiUtil.getOs().toString());
return r;
}
}

痛点:自己拼 OSHI 调用太啰嗦,改起来还要翻文档。
收益:Service 层封装 + Controller 暴露,监控页代码量从几十行压到不到 20 行。

接入 3 步:依赖 + 调用 + 封装

第 1 步:引依赖(两个都要)

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.41</version>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.4.1</version>
</dependency>

关键点:OshiUtil 是壳,oshi-core 是底层——少引一个就 ClassNotFoundException。Spring Boot 3 项目要用 Hutool 5.8.20 以上才兼容 Jakarta EE。

第 2 步:直接调(无配置)

OshiUtil 的所有静态方法都不用 new、不用注入,按需直接调。4 类信息按场景挑:

① 获取 CPU 信息

CpuInfo cpu = OshiUtil.getCpuInfo();
System.out.println("CPU 核心数:" + cpu.getCpuNum());
System.out.println("CPU 使用率:" + cpu.getUsed() + "%");
System.out.println("CPU 空闲率:" + cpu.getFree() + "%");

注意:getCpuInfo() 内部要做两次采样取差值,调用会阻塞约 1 秒——Web 接口里别直接调,用定时任务后台采(边界 1 详述)。

② 获取内存信息

GlobalMemory memory = OshiUtil.getMemory();
long totalGB = memory.getTotal() / 1024 / 1024 / 1024;
long availGB = memory.getAvailable() / 1024 / 1024 / 1024;
System.out.println("内存总量:" + totalGB + " GB");
System.out.println("可用内存:" + availGB + " GB");

单位永远是字节——Hutool 自带 FileUtil.readableFileSize(memory.getTotal()) 一行返回 "16.00 GB",比手撸除法优雅。

③ 获取操作系统信息

OperatingSystem os = OshiUtil.getOs();
System.out.println("OS 家族:" + os.getFamily()); // Linux / macOS / Windows
System.out.println("OS 版本:" + os.getVersionInfo()); // Ubuntu 22.04.3 LTS (build ...)
System.out.println("位数:" + os.getBitness()); // 64
System.out.println("启动时间:" + os.getSystemBootTime());

排查问题时「我现在到底跑在什么 OS 上」经常是第一步——K8s 集群同镜像跑在不同 Node,os.getFamily() 一行分清。

④ 获取 JVM 信息

JavaInfo java = SystemUtil.getJavaInfo();
System.out.println("Java 版本:" + java.getVersion()); // 17.0.10
System.out.println("JVM 厂商:" + java.getVendor()); // Eclipse Adoptium
System.out.println("Java 主目录:" + java.getHome());

// 也可以走 Runtime
Runtime rt = Runtime.getRuntime();
System.out.println("JVM 可用核数:" + rt.availableProcessors());
System.out.println("JVM 最大堆:" + rt.maxMemory() / 1024 / 1024 + " MB");

Hutool 的 SystemUtil 是 OshiUtil 的好搭档——SystemUtil 拿 JVM 层信息(不用 native),OshiUtil 拿 OS / 硬件层信息(走 JNA),两者配合覆盖 99% 的监控需求。

第 3 步:封装成 Bean(生产推荐)

把 OshiUtil 调用集中在一个 @Service 里,是为了:① 加缓存避开 CPU 采样阻塞;② 后续要换底层(迁 Micrometer 等)只改一处。

3 个真实使用边界

边界 1:CPU 使用率会阻塞约 1 秒(最常见)

OshiUtil.getCpuInfo() 内部要做两次采样取差值——调用会阻塞约 1 秒。Web 接口里直接调,TP99 立刻劣化。

修法:用 @Scheduled 每 10 秒后台采集一次写到内存变量,接口返回最近一次结果。绝不能在请求线程里同步采。

边界 2:Windows 系统负载永远是 -1(常见)

Windows 不暴露 Unix 风格的 loadavg,OSHI 在 Windows 上 getSystemLoadAverage() 直接返回 -1——这是 Windows 的特性,不是 bug。要监控就用 CPU 使用率,别用系统负载。

边界 3:容器化里拿到的是宿主机,不是 cgroup 限制(少见但破坏力大)

K8s pod 里 OshiUtil 跑出来的 CPU 核数和内存默认是宿主机的,不是容器分配的——监控接口报「16 核 / 64GB」,但容器其实只分到 2 核 / 4GB,运维一脸懵、告警阈值全乱。

根因是 OSHI 走 JNA 调系统接口,没主动感知 cgroup 限制。

修法:

  • JDK 17+:默认正确感知 cgroup v2,无需额外配置;
  • JDK 11+ / JDK 8u191+:自动启用 -XX:+UseContainerSupport;
  • JDK 8u131-190:手动加 -XX:+UseContainerSupport -XX:+UnlockExperimentalVMOptions;
  • 写监控接口前先验:Runtime.availableProcessors() 返回值要等于 K8s limits.cpu,不等就排查 JDK / cgroup 配置。

关键认知:OshiUtil 拿的是「OS 看到的数据」——容器场景下「OS 视角」和「容器配额」可能不一致,监控展示前必须对齐。

说到底

OshiUtil 的价值不是「拿到系统信息」——这件事 OSHI 自己就能做。它的价值是把「拿到系统信息」从一个需要查文档的活,压成了一个静态方法调用。好的工具库不是功能更强,是让 90% 的人不再需要看文档。

文章目录
  1. 1. 它不是「让 Java 拿到系统信息」,是「让你不直接用 OSHI」
  2. 2. 横向对比:4 个方案怎么选
  3. 3. 3 个真实场景:什么时候用得上
  4. 4. 接入 3 步:依赖 + 调用 + 封装
  5. 5. 3 个真实使用边界
  6. 6. 说到底