Skip to content

S03-10 核心类-系统类

[TOC]

java.lang.System

核心原理与类定义

java.lang.System 是 Java 核心类库中最基础的实用工具类之一。该类被声明为 public final class,意味着它不允许被任何子类继承。同时,其构造方法被私有化(private System() {}),从底层杜绝了外部实例化的可能性。

System 类的核心设计定位是作为 Java 虚拟机(JVM)与宿主操作系统(Host OS)之间的一座静态通信桥梁。它所暴露的所有属性和方法皆为 static(静态)修饰,内部大量依赖原生方法(Native Methods)实现,允许 Java 程序在不失平台无关性的前提下,安全地访问操作系统底层的资源、环境变量、标准输入输出流,并执行高性能的内存数组拷贝。

注意事项:

由于 System 类包含直接控制 JVM 行为和读取敏感系统环境的方法,在企业级安全架构或多租户沙箱环境中,频繁调用特定方法(如 System.exit() 或修改属性)可能会触发 SecurityException 异常(若配置了安全管理器)。此外,频繁调用其底层的 Native 方法可能会产生轻微的上下文切换开销。

java
package com.architecture.system;

/**
 * 验证 System 类的不可实例化与静态工具类特性
 */
public class SystemClassDefinitionDemo {
    public static void main(String[] args) {
        // 验证:System 类所有属性及方法均为静态,直接通过类名调用
        String javaVersion = System.getProperty("java.version");
        System.out.println("Java Runtime Version: " + javaVersion);

        // 注意:尝试通过反射强制实例化 System 将引发违规,其设计上即是不允许实例化的
        try {
            Class<?> clazz = Class.forName("java.lang.System");
            java.lang.reflect.Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            // 运行时通常会因为安全和构造函数私有化导致无法正常使用其非现有实例
            System.out.println("Constructor accessible: " + constructor.isAccessible());
        } catch (Exception e) {
            System.err.println("无法实例化 System 类: " + e.getMessage());
        }
    }
}

标准输入输出

System 类内部维护了三个核心的静态输入/输出流对象:inouterr。它们在 JVM 启动时被自动初始化,默认绑定到宿主系统的控制台。为了满足日志记录、自动化测试或流重定向的需求,System 提供了对应的 set 方法用于动态变更这些流的去向。

java
package com.architecture.system;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.ByteArrayInputStream;

/**
 * 标准流对象的重定向与控制示例
 */
public class SystemStreamDemo {
public static void main(String[] args) throws Exception {
// 1. 备份原有的标准输出流
PrintStream originalOut = System.out;

       // 2. 准备捕获输出的字节数组流
       ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
       PrintStream customOut = new PrintStream(outputBuffer);

       // 3. 执行标准输出流重定向
       System.setOut(customOut);

       // 此时的打印内容不会显示在控制台,而是进入了 outputBuffer
       System.out.print("This data is intercepted.");

       // 4. 恢复标准输出流
       System.setOut(originalOut);

       // 5. 验证拦截到的数据并输出
       String interceptedData = outputBuffer.toString();
       System.out.println("拦截到的标准输出内容: " + interceptedData);

       // 6. 重定向标准输入流示例
       String simulatedInput = "Automated Input Data";
       InputStream customIn = new ByteArrayInputStream(simulatedInput.getBytes());
       System.setIn(customIn);

       // 读取重定向后的输入流
       byte[] buffer = new byte[100];
       int length = System.in.read(buffer);
       System.out.println("重定向输入流读取结果: " + new String(buffer, 0, length));

}
}

系统属性与环境配置

System 类提供了访问 Java 虚拟机系统属性(System Properties)和操作系统环境变量(Environment Variables)的方法。

  • 系统属性:由 JVM 内部维护,可以通过 -Dproperty=value 命令行参数显式传入,生命周期与 JVM 进程绑定。

  • 环境变量:属于操作系统全局或当前用户上下文的配置,Java 程序对其只有只读权限。

    java
    package com.architecture.system;
    
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * 读取与操纵系统属性及环境变量示例
     */
    public class SystemPropertiesDemo {
    public static void main(String[] args) {
    // 1. 获取特定的系统属性
    String osName = System.getProperty("os.name");
    String fileEncoding = System.getProperty("file.encoding");
    System.out.println("操作系统名称: " + osName);
    System.out.println("系统默认编码: " + fileEncoding);
    
           // 2. 动态写入自定义系统属性
           System.setProperty("custom.app.mode", "PROD");
           System.out.println("自定义属性 custom.app.mode: " + System.getProperty("custom.app.mode"));
    
           // 3. 遍历读取部分系统属性
           Properties properties = System.getProperties();
           System.out.println("全局属性总数: " + properties.size());
    
           // 4. 获取指定的操作系统环境变量
           String pathVariable = System.getenv("PATH");
           System.out.println("系统环境变量 PATH (前50个字符): " +
               (pathVariable != null && pathVariable.length() > 50 ? pathVariable.substring(0, 50) + "..." : pathVariable));
    
           // 5. 遍历操作系统环境变量只读映射
           Map<String, String> envMap = System.getenv();
           if (!envMap.isEmpty()) {
               String firstKey = envMap.keySet().iterator().next();
               System.out.println("环境变量示例 -> " + firstKey + " = " + envMap.get(firstKey));
           }
    
    }
    }

数组复制与高性能操作

System.arraycopy 是一个纯 Native 方法,其底层通过 C/C++ 直接操作连续的内存块(类似于 C 语言中的 memcpy)。其运行效率远高于传统的 for 循环赋值以及 Arrays.copyOf(后者内部最终也是调用 System.arraycopy)。

注意事项:

  1. 类型兼容性:源数组与目标数组的数据类型必须完全兼容,否则抛出 ArrayStoreException
  2. 边界越界:若 srcPos + length > src.lengthdestPos + length > dest.length,将触发 IndexOutOfBoundsException
  3. 浅拷贝风险:对于引用类型数组,System.arraycopy 执行的是浅拷贝(Shallow Copy),复制的仅是对象的引用地址,而非对象本身。
java
package com.architecture.system;

/**
 * 高性能数组拷贝与底层操作示例
 */
public class SystemNativeOpsDemo {
public static void main(String[] args) {
int[] sourceArray = {10, 20, 30, 40, 50};
int[] targetArray = new int[8];

       // 1. 将 sourceArray 中从索引 1 开始的 3 个元素,拷贝到 targetArray 中从索引 2 开始的位置
       System.arraycopy(sourceArray, 1, targetArray, 2, 3);

       // 输出拷贝后的目标数组元素
       System.out.print("targetArray 复制后内容: ");
       for (int val : targetArray) {
           System.out.print(val + " ");
       }
       System.out.println();

       // 2. 验证 identityHashCode 与重写 hashCode 的区别
       String str1 = new String("CustomHash");
       String str2 = new String("CustomHash");

       System.out.println("str1 逻辑 hashCode: " + str1.hashCode());
       System.out.println("str2 逻辑 hashCode: " + str2.hashCode());

       // 尽管内容相同、逻辑哈希相同,但两个对象在 JVM 堆中的物理地址/原始标识不同
       System.out.println("str1 原始 identityHashCode: " + System.identityHashCode(str1));
       System.out.println("str2 原始 identityHashCode: " + System.identityHashCode(str2));

}
}

时间计量与进程控制

在需要度量时间跨度或需要强行干预当前 JVM 运行进程时,System 提供了高精度的时间接口和硬件级别的退出命令。

注意事项:

  1. System.currentTimeMillis() 的返回值依赖于操作系统底层的系统时钟(墙上时钟),用户修改系统时间会导致该值发生跳变,因此严禁用于计算代码耗时/性能基准测试
  2. System.nanoTime() 专为高精度时间间隔测量设计,其初始参照点是任意的,不可用于计算具体的年月日日期
  3. 调用 System.exit(status) 将导致整个 JVM 实例无条件关闭。在分布式框架或 Web 容器中应规避此操作,以免意外关停整个服务。
java
package com.architecture.system;

/**
 * 时间计量工具的精度对比示例
 */
public class SystemTimeDemo {
public static void main(String[] args) {
// 1. 获取绝对系统当前时间(毫秒级)
long currentEpochMillis = System.currentTimeMillis();
System.out.println("当前系统 Unix 时间戳 (毫秒): " + currentEpochMillis);

       // 2. 评估高精度代码块执行耗时(纳秒级)
       long startTime = System.nanoTime();

       // 模拟执行一段计算密集型逻辑
       double sum = 0;
       for (int i = 0; i < 1_000_000; i++) {
           sum += Math.sin(i);
       }

       long endTime = System.nanoTime();
       long durationNanoseconds = endTime - startTime;

       System.out.println("计算结果收敛值: " + sum);
       System.out.println("逻辑执行耗时 (纳秒): " + durationNanoseconds);
       System.out.println("逻辑执行耗时 (毫秒): " + (durationNanoseconds / 1_000_000.0));

}
}

API:System

标准输入输出与错误流

  • 属性
  • final InputStream in,标准输入流。默认接收来自键盘或其他输入源的数据。
  • final PrintStream out,标准输出流。默认将输出信息打印至控制台。
  • final PrintStream err,标准错误输出流。专用于输出错误或警告信息,在多数 IDE 中默认以红色字体标出。
  • 方法
  • void setIn()(InputStream in),重新分配系统的标准输入流,替换默认的 System.in
  • void setOut()(PrintStream out),重新分配系统的标准输出流,替换默认的 System.out
  • void setErr()(PrintStream err),重新分配系统的标准错误输出流,替换默认的 System.err

系统属性与环境变量获取

  • String getProperty()(String key),根据指定的键名获取对应的系统属性(如 os.name, user.home)。
  • Properties getProperties()(),获取当前 JVM 中所有的系统属性集合。
  • String setProperty()(String key, String value),动态设置或修改指定的系统属性。
  • String getenv()(String name),获取操作系统中指定的全局环境变量值(如 PATH, JAVA_HOME)。
  • Map<String,String> getenv()(),返回当前系统所有环境变量的只读全局映射表(Map)。

内存与低级系统操作

  • void arraycopy()(Object src, int srcPos, Object dest, int destPos, int length),从源数组的指定索引开始,复制指定长度的数据到目标数组的指定位置。
  • void gc()(),显式向虚拟机建议腾出内存来运行垃圾回收器。该调用不保证 JVM 会立即执行回收。
  • int identityHashCode()(Object x),无论指定对象的类是否重写了 hashCode() 方法,均返回该对象最原始的、与默认实现相同的哈希码。

时间戳获取与进程终止

  • long currentTimeMillis()(),返回当前时间与协调世界时(UTC)1970 年 1 月 1 日午夜之间的时间差(以毫秒为单位)。
  • long nanoTime()(),返回正在运行的 JVM 的高精度时间源的当前值(以纳秒为单位),用于计算高精度的代码执行耗时。
  • void exit()(int status),强行终止当前正在运行的 Java 虚拟机。0 表示正常退出,非 0 表示异常终止。

实战:系统健康度检查

下面是一个适合初学者的完整、高质量、可直接运行的系统健康度检查与诊断工具。该案例综合运用了前面章节所介绍的 System 核心 API(包含系统属性读取、高精度性能耗时度量、本地数组高效拷贝、流的动态监控)。

java
package com.architecture.system;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

/**
 * 实验级综合案例:系统运行环境健康度与基础性能诊断工具
 */
public class SystemDiagnosticTool {

    public static void main(String[] args) {
        System.out.println("=== 开始执行系统深度诊断 ===\n");

        // 1. 收集环境基本信息
        printEnvironmentReport();

        System.out.println("\n--- 开始执行内存数组拷贝性能测试 ---");
        // 2. 诊断底层数组高频拷贝的性能表现
        runArrayCopyBenchmark();

        System.out.println("\n--- 开始执行内部异常日志重定向测试 ---");
        // 3. 测试标准错误流的动态捕获
        testErrorStreamRedirection();

        System.out.println("\n=== 系统深度诊断全部完成 ===");
    }

    /**
     * 读取并打印关键的基础运行环境报告
     */
    private static void printEnvironmentReport() {
        System.out.println("[基本信息] 操作系统: " + System.getProperty("os.name")
                + " (" + System.getProperty("os.arch") + ")");
        System.out.println("[基本信息] 虚拟机实现: " + System.getProperty("java.vm.name"));
        System.out.println("[基本信息] Java 版本: " + System.getProperty("java.version"));
        System.out.println("[基本信息] 当前用户主目录: " + System.getProperty("user.home"));
    }

    /**
     * 基于 System.nanoTime 和 System.arraycopy 评估底层内存拷贝效率
     */
    private static void runArrayCopyBenchmark() {
        int size = 10_000_000;
        int[] source = new int[size];
        int[] target = new int[size];

        // 为源数组赋初始值
        for (int i = 0; i < size; i++) {
            source[i] = i;
        }

        // 记录开始纳秒时间
        long startNano = System.nanoTime();

        // 调用 Native 方法进行千万级数据的高效内存块拷贝
        System.arraycopy(source, 0, target, 0, size);

        // 记录结束纳秒时间
        long endNano = System.nanoTime();

        long elapsedTimeNano = endNano - startNano;
        double elapsedTimeMillis = elapsedTimeNano / 1_000_000.0;

        System.out.println("[性能测试] 成功复制 " + size + " 个整型元素的元素数组。");
        System.out.println("[性能测试] 耗时: " + elapsedTimeNano + " 纳秒 (" + elapsedTimeMillis + " 毫秒)");
    }

    /**
     * 演示如何通过重定向 System.err 捕获组件输出的隐式错误
     */
    private static void testErrorStreamRedirection() {
        // 备份标准的错误流
        PrintStream originalErr = System.err;

        // 建立缓冲区接管输出
        ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
        PrintStream customErr = new PrintStream(errBuffer);

        // 重定向
        System.setErr(customErr);

        // 模拟第三方组件在运行期间向 err 流抛出警告信息
        System.err.print("WARNING: Low memory threshold warning detected inside underlying native worker.");

        // 还原错误流
        System.setErr(originalErr);

        // 提取重定向期间捕获的错误日志
        String capturedLog = errBuffer.toString();
        System.out.println("[日志捕获] 成功拦截到潜在的系统错误日志:");
        System.out.println(" >> " + capturedLog);
    }
}

java.lang.Runtime

核心原理

java.lang.Runtime 是 Java 核心类库中用于与 Java 虚拟机(JVM)运行环境进行交互的桥梁。每个 Java 应用程序在运行期间都拥有一个唯一的 Runtime 实例。

单例模式设计:

Runtime 类采用了饿汉式单例模式。其构造方法被声明为 private,阻止了外部直接通过 new 关键字创建实例。应用程序必须通过静态方法 Runtime.getRuntime() 获取当前运行时的唯一实例引用。

与 JVM 进程的纽带关系:

Runtime 实例代表了当前 JVM 进程的底层控制权。通过它,Java 程序可以向 JVM 申请内存状态查询、强制执行垃圾回收、注册进程销毁钩子,甚至直接调用操作系统底层的可执行程序。

java
package com.runtime.core;

public class RuntimeInstanceDemo {
    public static void main(String[] args) {
        // 1. 获取当前主线程运行环境的 Runtime 实例
        Runtime runtime1 = Runtime.getRuntime();

        // 2. 再次获取实例,验证单例特性
        Runtime runtime2 = Runtime.getRuntime();

        // 3. 验证两个引用是否指向堆内存中的同一个对象
        boolean isSameInstance = (runtime1 == runtime2);
        System.out.println("runtime1 是否与 runtime2 为同一实例: " + isSameInstance);

        // 4. 获取当前设备的 CPU 核心数
        int processors = runtime1.availableProcessors();
        System.out.println("当前系统可用 CPU 核心数: " + processors);
    }
}

内存管理与垃圾回收

Runtime 提供了直接读取 JVM 堆内存指标的接口,这些指标是进行应用性能调优和内存泄漏排查的基础。

内存指标定义:

  • 最大可用内存 (maxMemory):JVM 尝试使用的最大内存量(对应参数 -Xmx)。
  • 总分配内存 (totalMemory):JVM 当前已经从操作系统申请到的堆内存总量(对应参数 -Xms 会随需求增长,但不会超过 -Xmx)。
  • 空闲内存 (freeMemory):在已分配的总内存中,目前尚未被使用的剩余空间。

注意事项:

Runtime.getRuntime().gc() 方法的调用仅仅是建议 JVM 进行垃圾回收(Full GC),JVM 并不保证会立即响应或执行回收动作。在生产环境中,频繁显式调用 gc() 会引发 STW(Stop-The-World),导致应用出现明显的卡顿,应当尽量避免。

java
package com.runtime.memory;

public class RuntimeMemoryDemo {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();

        // 转换单位为 MB 的系数
        long mb = 1024 * 1024;

        System.out.println("--- JVM 初始内存状态 ---");
        System.out.println("最大可用内存 (Max Memory): " + (runtime.maxMemory() / mb) + " MB");
        System.out.println("当前已分配总内存 (Total Memory): " + (runtime.totalMemory() / mb) + " MB");
        System.out.println("当前空闲内存 (Free Memory): " + (runtime.freeMemory() / mb) + " MB");

        // 创建一个大数组以消耗内存
        byte[] memoryConsumer = new byte[50 * 1024 * 1024]; // 显式消耗约 50MB 空间

        System.out.println("\n--- 分配 50MB 数组后的内存状态 ---");
        System.out.println("当前已分配总内存 (Total Memory): " + (runtime.totalMemory() / mb) + " MB");
        System.out.println("当前空闲内存 (Free Memory): " + (runtime.freeMemory() / mb) + " MB");

        // 释放引用并建议 JVM 进行垃圾回收
        memoryConsumer = null;
        runtime.gc();

        System.out.println("\n--- 显式调用 gc() 后的内存状态 ---");
        System.out.println("当前空闲内存 (Free Memory): " + (runtime.freeMemory() / mb) + " MB");
    }
}

外部进程管理

通过 Runtime.exec() 方法,Java 程序能够派生(fork)出一个子进程来执行宿主操作系统上的命令或可执行文件。

进程交互机制:

exec() 方法执行后会返回一个 java.lang.Process 对象。通过该对象,可以控制子进程的输入、输出以及等待其执行结束。

注意事项:

输入/输出流堵塞风险:子进程的物理输入/输出流被重定向到了 JVM 管理的管道中。如果子进程持续向标准输出或标准错误流写入数据,而 Java 主进程没有及时读取(消费)这些流的数据,操作系统的缓冲区一旦被填满,子进程就会被永久阻塞(挂起)。因此,必须开启独立的线程去消费子进程的 InputStreamErrorStream

java
package com.runtime.process;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class RuntimeProcessDemo {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        Process process = null;

        try {
            // 根据操作系统类型决定执行的命令
            String os = System.getProperty("os.name").toLowerCase();
            String[] command;
            if (os.contains("win")) {
                command = new String[]{"cmd.exe", "/c", "ping 127.0.0.1 -n 3"};
            } else {
                command = new String[]{"ping", "-c", "3", "127.0.0.1"};
            }

            // 执行外部命令
            process = runtime.exec(command);

            // 必须及时消费流,避免外部进程阻塞
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), "GBK"))) { // Windows 下通常为 GBK,Linux/Mac 为 UTF-8
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println("[外部进程输出] " + line);
                }
            }

            // 等待进程执行完毕并获取退出码
            int exitCode = process.waitFor();
            System.out.println("外部进程执行结束,退出码为: " + exitCode);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (process != null) {
                process.destroy(); // 确保释放子进程资源
            }
        }
    }
}

JVM 生命周期钩子与退出

Runtime 允许开发者干预 JVM 进程的关闭行为。

关闭钩子(Shutdown Hook):

关闭钩子是一个已初始化但尚未启动的线程。当 JVM 因为常规原因(如 System.exit()、代码执行完毕、接收到系统的 SIGTERM 信号等)触发关闭时,JVM 会并发地启动所有已注册的关闭钩子。它们通常用于释放外部资源(如关闭数据库连接池、删除临时文件、释放锁等)。

注意事项:

  1. 关闭钩子内部必须是线程安全的,因为多个钩子之间会并发执行,次序无法保证。
  2. 钩子代码应当快速执行完毕,切勿执行耗时长的 I/O 或死循环,否则会导致 JVM 进程无法正常退出。
  3. Runtime.getRuntime().halt(int status) 会强行终止当前 JVM 进程,此时不会触发任何关闭钩子。
java
package com.runtime.lifecycle;

public class RuntimeHookDemo {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();

        // 1. 注册关闭钩子
        Thread shutdownHook = new Thread(() -> {
            System.out.println("\n[Shutdown Hook] 检测到 JVM 即将关闭,正在释放系统物理资源...");
            try {
                Thread.sleep(1000); // 模拟资源释放过程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("[Shutdown Hook] 资源释放完毕,善后工作结束。");
        });

        runtime.addShutdownHook(shutdownHook);

        System.out.println("应用程序主业务开始执行...");
        try {
            Thread.sleep(2000); // 模拟正在运行的业务
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("应用程序主业务正常结束,即将退出。");

        // 主线程正常结束,JVM 触发关闭,此时将自动执行上面注册的 shutdownHook 线程
    }
}

API:Runtime

基础与环境信息组

  • Runtime getRuntime()(),返回与当前 Java 应用程序相关的唯一运行时对象。

  • int availableProcessors()(),返回 Java 虚拟机可用的处理器核心数量。

  • Runtime.Version version()(),返回 Java 虚拟机的版本信息(JDK 9+ 引入)。

    java
    Runtime run = Runtime.getRuntime();
    int cpuCores = run.availableProcessors();
    Runtime.Version jvmVersion = Runtime.version();

内存管理功能组

  • long freeMemory()(),返回 Java 虚拟机中的空闲内存量(单位:字节)。

  • long totalMemory()(),返回 Java 虚拟机中的内存总量(单位:字节)。

  • long maxMemory()(),返回 Java 虚拟机试图使用的最大内存量(单位:字节)。

  • void gc()(),运行垃圾回收器。调用此方法暗示 JVM 尽最大努力回收未用对象。

    java
    Runtime run = Runtime.getRuntime();
    long free = run.freeMemory();
    long total = run.totalMemory();
    long max = run.maxMemory();
    run.gc();

进程管理功能组

  • Process exec()(String[] cmdarray),在单独的进程中执行指定命令和参数。

  • Process exec()(String command),在单独的进程中执行指定的字符串命令(注:在较新 JDK 版本中已不推荐使用,建议改用 ProcessBuilder 或数组传参形式)。

    java
    Runtime run = Runtime.getRuntime();
    String[] cmd = {"echo", "hello"};
    Process p = run.exec(cmd);

生命周期管理功能组

  • void addShutdownHook()(Thread hook),注册一个新的虚拟机关闭钩子。

  • boolean removeShutdownHook()(Thread hook),注销一个先前已注册的虚拟机关闭钩子。

  • void exit()(int status),通过启动虚拟机的关闭序列来终止当前正在运行的 Java 虚拟机。

  • void halt()(int status),强行终止当前正在运行的 Java 虚拟机,不触发关闭钩子。

    java
    Runtime run = Runtime.getRuntime();
    Thread myHook = new Thread(() -> System.out.println("Closed"));
    run.addShutdownHook(myHook);
    run.removeShutdownHook(myHook);
    // run.exit(0); 正常终止
    // run.halt(1); 强制崩溃式终止

实战:系统运行状态监控

本案例实现一个全方位的系统运行状态监控与安全退出工具。该工具在程序启动时动态监控 JVM 内存指标与 CPU 资源,并注册了关闭钩子以确保程序在遭遇突发终止时能够安全地保存业务日志。

java
package com.runtime.example;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SystemMonitorApplication {

  private static final String LOG_FILE = "app_shutdown_log.txt";

  public static void main(String[] args) {
    Runtime runtime = Runtime.getRuntime();

    // 1. 注册高质量的关闭钩子 (确保在异常断电/中断时保存核心状态)
    runtime.addShutdownHook(new Thread(() -> {
      String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
      String logContent = String.format("[%s] 警告: 检测到JVM进程退出信号,安全保存系统日志成功。\n", timeStamp);

      try (BufferedWriter writer = new BufferedWriter(new FileWriter(LOG_FILE, true))) {
        writer.write(logContent);
        System.out.println("[系统善后] 核心业务状态已持久化至文本: " + LOG_FILE);
      } catch (IOException e) {
        System.err.println("[错误] 关闭钩子执行期间写入日志失败: " + e.getMessage());
      }
    }));

    System.out.println("=========================================");
    System.out.println("     欢迎使用高阶系统运行状态诊断工具       ");
    System.out.println("=========================================");

    // 2. 动态读取并展示运行环境指标
    int processors = runtime.availableProcessors();
    long maxMem = runtime.maxMemory() / 1024 / 1024;
    long totalMem = runtime.totalMemory() / 1024 / 1024;
    long freeMem = runtime.freeMemory() / 1024 / 1024;
    long usedMem = totalMem - freeMem;

    System.out.println("操作系统可用 CPU 核心数 : " + processors + " 核");
    System.out.println("JVM 允许最大堆内存限制 : " + maxMem + " MB");
    System.out.println("JVM 当前已向系统申请总额 : " + totalMem + " MB");
    System.out.println("JVM 堆内当前已使用内存  : " + usedMem + " MB");
    System.out.println("JVM 堆内当前剩余空闲内存 : " + freeMem + " MB");
    System.out.println("=========================================");

    // 3. 模拟健康度自检
    double memoryUsageRatio = (double) usedMem / totalMem;
    if (memoryUsageRatio > 0.8) {
      System.out.println("[诊断结果] 警告:当前内存占用率超过 80%,建议缩减对象生命周期。");
      System.out.println("[诊断动作] 尝试执行紧急轻量级垃圾回收...");
      runtime.gc();
    } else {
      System.out.println("[诊断结果] 系统状态良好,内存指标处于安全范围内。");
    }

    System.out.println("\n模拟长周期业务运行中...(进程将在 3 秒后安全退出)");
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    System.out.println("业务完成,触发常规显式关闭。");
    // 4. 显式调用 exit 会正常引发关闭钩子工作
    System.exit(0);
  }
}