凌峰创科服务平台

Java如何高效监控Linux服务器资源?

下面我将从 核心思路、常用工具、实践代码示例 三个方面,为你详细解析如何使用 Java 进行 Linux 服务器监控。

Java如何高效监控Linux服务器资源?-图1
(图片来源网络,侵删)

核心思路

Java 本身是一个运行在 JVM 上的应用,它不直接与操作系统内核交互,要监控 Linux 服务器,Java 程序必须通过某种方式来获取操作系统的信息,主要有以下几种方式:

  1. 执行 Shell 命令 (最直接、最灵活)

    • 原理: Java 的 Runtime.getRuntime().exec()ProcessBuilder 类可以启动一个外部进程,执行 Linux 命令(如 top, free, df, iostat 等),然后读取命令的输出结果。
    • 优点: 几乎可以获取任何 Linux 命令能提供的信息,非常灵活。
    • 缺点: 性能开销相对较大,需要解析文本输出,当命令格式变化时,解析逻辑也需要更新。不推荐在生产环境中频繁执行高开销命令
  2. 使用 JNI (Java Native Interface)

    • 原理: 通过 JNI 调用 C/C++ 编写的本地库,这些本地库可以直接调用 Linux 内核的 API(如 /proc 文件系统、sysfs)来获取系统信息。
    • 优点: 性能极高,功能强大,可以获取到最底层的系统状态。
    • 缺点: 开发复杂,需要处理 C/C++ 和 Java 之间的数据类型转换和内存管理,并且会失去 Java 的跨平台优势。
  3. 使用成熟的 Java 监控库 (推荐)

    Java如何高效监控Linux服务器资源?-图2
    (图片来源网络,侵删)
    • 原理: 很多优秀的开源库已经封装了上述两种方式,提供了简单易用的 Java API 来获取系统信息。
    • 优点: 开发效率高,API 友好,性能经过优化,跨平台性好(这些库通常也支持 Windows/macOS)。
    • 缺点: 功能可能不如直接调用命令或 JNI 那么无所不能,但对于 95% 的监控场景已经足够。

对于绝大多数 Java 使用成熟的 Java 监控库是最佳实践,只有在有极其特殊的高性能或底层需求时,才考虑 JNI。


常用工具和库

这里重点推荐几个在 Java 社区中广受好评的监控库。

OSHI (推荐首选)

  • GitHub: https://github.com/oshi/oshi
  • 简介: OSHI 是一个纯 Java 的、跨平台的、轻量级的系统信息库,它不依赖任何本地库,通过解析 /proc, /sys, netstat, lsof, wmic (Windows) 等系统文件和命令输出来获取信息。
  • 优点:
    • 纯 Java: 无需 JNI,部署简单。
    • 跨平台: 一套代码,Linux/Windows/macOS/Unix 均可用。
    • 功能全面: 涵盖了 CPU、内存、磁盘、网络、进程等几乎所有核心指标。
    • 轻量级: 启动快,内存占用小。
  • Maven 依赖:
    <dependency>
        <groupId>com.github.oshi</groupId>
        <artifactId>oshi-core</artifactId>
        <version>6.4.0</version> <!-- 请使用最新版本 -->
    </dependency>

Sigar (功能强大,但较老)

  • GitHub: https://github.com/hyperic/sigar
  • 简介: Sigar 是一个非常经典的系统信息库,通过 JNI 调用其 C 语言库来获取系统信息,因此性能和功能都非常强大。
  • 优点:
    • 性能极高: 直接与内核交互。
    • 功能极其丰富: 信息非常详尽。
  • 缺点:
    • 依赖本地库: 需要在服务器上部署对应平台的 .so (Linux) 或 .dll (Windows) 文件,增加了部署复杂度。
    • 项目更新缓慢: 最后一次更新已是多年前。
  • Maven 依赖:
    <dependency>
        <groupId>org.fusesource</groupId>
        <artifactId>sigar</artifactId>
        <version>1.6.4</version>
    </dependency>

使用 JMX (Java Management Extensions)

  • 简介: JMX 是 Java 标准的管理和监控规范,如果你的监控目标是一个 Java 应用(如 Tomcat, Spring Boot),JMX 是最标准、最强大的方式。
  • 工作方式: Java 应用可以通过 MBean (Managed Bean) 暴露其内部状态(如内存池、线程数、自定义业务指标),监控程序(如 JConsole, VisualVM, Prometheus + JMX Exporter)可以连接到应用的 JMX 端口,读取这些 MBean 的数据。
  • 优点:
    • 标准规范: 无需额外库,JDK 自带。
    • 功能强大: 可以监控应用内部细节,甚至可以动态修改配置。
    • 远程监控: 支持通过网络进行远程监控。
  • 缺点:
    • 仅限 Java 应用: 无法直接监控操作系统或非 Java 进程。
    • 需要应用支持: 目标应用必须开启并正确配置 JMX。

实践代码示例 (使用 OSHI)

下面我们使用 OSHI 库来编写一个 Java 程序,监控 Linux 服务器的核心指标。

项目准备

创建一个 Maven 项目,并在 pom.xml 中添加 OSHI 依赖。

Java如何高效监控Linux服务器资源?-图3
(图片来源网络,侵删)
<dependencies>
    <dependency>
        <groupId>com.github.oshi</groupId>
        <artifactId>oshi-core</artifactId>
        <version>6.4.0</version>
    </dependency>
    <!-- 为了更好的格式化输出,可以加一个 Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.26</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

编写监控代码

创建一个 ServerMonitor.java 文件。

import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.OperatingSystem;
import oshi.util.Util;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
 * 使用 OSHI 库进行 Linux 服务器监控
 */
public class ServerMonitor {
    private static final SystemInfo si = new SystemInfo();
    private static final HardwareAbstractionLayer hal = si.getHardware();
    private static final OperatingSystem os = si.getOperatingSystem();
    private static final DecimalFormat df = new DecimalFormat("0.00");
    public static void main(String[] args) throws InterruptedException {
        // 初始化 CPU 上下文,用于计算 CPU 使用率
        CentralProcessor processor = hal.getProcessor();
        long[] prevTicks = processor.getSystemCpuLoadTicks();
        System.out.println("===== Linux 服务器监控开始 =====");
        System.out.println("服务器时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        System.out.println("操作系统: " + os.getFamily() + " " + os.getVersionInfo());
        System.out.println("=====================================\n");
        while (true) {
            printSystemInfo();
            printCpuInfo(processor, prevTicks);
            printMemoryInfo();
            printDiskInfo();
            printNetworkInfo();
            System.out.println("\n-------------------------------------\n");
            // 每 5 秒刷新一次
            Thread.sleep(5000);
        }
    }
    private static void printSystemInfo() {
        System.out.println("--- 系统概览 ---");
        System.out.println("主机名: " + os.getNetworkParams().getHostName());
        System.out.println("制造商: " + hal.getComputerSystem().getManufacturer());
        System.out.println("型号: " + hal.getComputerSystem().getModel());
        System.out.println("CPU 逻辑核心数: " + hal.getProcessor().getLogicalProcessorCount());
        System.out.println("CPU 物理核心数: " + hal.getProcessor().getPhysicalProcessorCount());
    }
    private static void printCpuInfo(CentralProcessor processor, long[] prevTicks) {
        System.out.println("\n--- CPU 信息 ---");
        double cpuLoad = processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100;
        System.out.println("CPU 使用率: " + df.format(cpuLoad) + "%");
        System.out.println("CPU 上下文切换/秒: " + processor.getSystemCpuLoadTicks()[2]); // ctx switches
        System.out.println("CPU 中断/秒: " + processor.getSystemCpuLoadTicks()[3]); // interrupts
        // 更新 ticks 用于下一次计算
        prevTicks = processor.getSystemCpuLoadTicks();
    }
    private static void printMemoryInfo() {
        System.out.println("\n--- 内存信息 ---");
        GlobalMemory memory = hal.getMemory();
        long total = memory.getTotal();
        long available = memory.getAvailable();
        long used = total - available;
        double usage = (double) used / total * 100;
        System.out.println("总内存: " + formatBytes(total));
        System.out.println("已用内存: " + formatBytes(used) + " (" + df.format(usage) + "%)");
        System.out.println("可用内存: " + formatBytes(available) + " (" + df.format(100 - usage) + "%)");
    }
    private static void printDiskInfo() {
        System.out.println("\n--- 磁盘信息 ---");
        hal.getDiskStores().forEach(diskStore -> {
            System.out.println("磁盘名称: " + diskStore.getName());
            System.out.println("磁盘读取: " + formatBytes(diskStore.getReadBytes()) + " / " + diskStore.getReadCount() + " 次");
            System.out.println("磁盘写入: " + formatBytes(diskStore.getWriteBytes()) + " / " + diskStore.getWriteCount() + " 次");
        });
    }
    private static void printNetworkInfo() {
        System.out.println("\n--- 网络信息 ---");
        hal.getNetworkIFs().forEach(networkIF -> {
            System.out.println("网络接口: " + networkIF.getName());
            System.out.println("网络发送: " + formatBytes(networkIF.getBytesSent()) + " / " + networkIF.getPacketsSent() + " 个包");
            System.out.println("网络接收: " + formatBytes(networkIF.getBytesRecv()) + " / " + networkIF.getPacketsRecv() + " 个包");
        });
    }
    // 格式化字节大小
    private static String formatBytes(long bytes) {
        if (bytes < 1024) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(1024));
        char pre = "KMGTPE".charAt(exp - 1);
        return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
    }
}

运行和解读

  1. 将代码编译打包成 JAR 文件。
  2. 将 JAR 文件上传到你的 Linux 服务器。
  3. 运行 java -jar your-jar-file.jar

你将看到类似下面格式的输出,并且每 5 秒刷新一次:

===== Linux 服务器监控开始 =====
服务器时间: 2025-10-27 10:30:00
操作系统: Linux 5.4.0-150-generic
=====================================
--- 系统概览 ---
主机名: server-01
制造商: innotek GmbH
型号: VirtualBox
CPU 逻辑核心数: 4
CPU 物理核心数: 2
--- CPU 信息 ---
CPU 使用率: 5.20%
CPU 上下文切换/秒: 1200
CPU 中断/秒: 350
--- 内存信息 ---
总内存: 7.63 GB
已用内存: 2.10 GB (27.55%)
可用内存: 5.53 GB (72.45%)
--- 磁盘信息 ---
磁盘名称: sda
磁盘读取: 1.2 GB / 5000 次
磁盘写入: 500 MB / 3000 次
--- 网络信息 ---
网络接口: eth0
网络发送: 100 MB / 1000 个包
网络接收: 500 MB / 5000 个包
-------------------------------------

进阶:构建一个完整的监控系统

上面的代码只是一个简单的命令行工具,一个生产级的监控系统通常包含以下部分:

  1. 数据采集端 (Agent):

    • 在每台需要监控的服务器上部署一个轻量级的 Agent 程序。
    • 这个 Agent 可以使用 OSHI 或其他库,定期(如每 15 秒)采集服务器指标。
    • 将采集到的数据格式化(如 JSON),并推送到一个中央存储。
  2. 数据存储:

    • 时序数据库: 这是监控数据存储的最佳选择。InfluxDB, Prometheus, VictoriaMetrics,它们专门为时间序列数据优化,具有高效的写入和查询性能。
    • 数据库: 也可以使用 MySQL, PostgreSQL, 但需要自行设计表结构和索引,性能通常不如时序数据库。
  3. 数据展示与告警:

    • 可视化工具:
      • Grafana: 业界标准,可以连接各种数据源(包括 InfluxDB, Prometheus),通过图表、仪表盘直观展示数据。
      • Prometheus UI: 如果使用 Prometheus,其自带的 UI 也可以进行查询和展示。
    • 告警系统:
      • Alertmanager: Prometheus 生态系统的一部分,负责处理告警规则,并通过邮件、Slack、Webhook 等方式发送通知。
      • Grafana Alerting: Grafana 内置的告警功能,可以基于图表数据设置阈值触发告警。

架构图示例

+----------+      +-----------------+      +-------------+      +-------------+
|          |      |                 |      |             |      |             |
|  Server  |----->|   Java Agent    |----->|   InfluxDB  |----->|   Grafana   |
|  (Node1) |      | (使用 OSHI 采集) |      | (存储时序数据) |      | (可视化展示) |
|          |      |                 |      |             |      |             |
+----------+      +-----------------+      +-------------+      +-------------+
      ^                                           |                       |
      |                                           |                       | (告警规则)
      |                                           v                       |
      |                                     +-------------+               |
      |                                     | Alertmanager|<--------------+
      |                                     | (发送通知)  |
      +------------------------------------>+-------------+
  • 入门和快速实现: 使用 OSHI 库,它能满足大部分服务器监控需求,且开发简单,部署方便。
  • 深度监控 Java 应用: 使用 JMX,这是监控和管理 Java 应用的标准方式。
  • 构建企业级监控: 采用 Agent + 时序数据库 + 可视化工具 的架构,Java 可以用来编写 Agent,将数据发送到 InfluxDBPrometheus,然后用 Grafana 做出漂亮的监控大盘和告警。

希望这份详细的指南能帮助你开始 Java Linux 服务器监控的实践!

分享:
扫描分享到社交APP
上一篇
下一篇