一、核心概念

java.lang.System 是 Java 提供的系统级工具类,封装了与 JVM 和操作系统交互的关键功能。它不是普通的工具类,而是 JVM 的“门面”(Facade),提供了对底层资源的访问能力。

主要功能模块:

模块 功能
I/O 重定向 setIn(), setOut(), setErr()
内存与 GC 控制 gc(), runFinalization()(已废弃)
系统信息获取 currentTimeMillis(), nanoTime(), getProperty(), getenv()
对象身份识别 identityHashCode()
数组操作 arraycopy()(高效内存拷贝)
系统退出 exit(), halt()

🔑 本质System 类通过 JNI(Java Native Interface) 调用 JVM 内部 C/C++ 实现,是 Java 程序与 JVM/OS 通信的桥梁。


二、操作步骤(详细)

✅ 操作 1:重定向标准输入/输出(setIn/setOut/setErr

步骤 1:保存原始流(推荐)

InputStream originalIn = System.in;
PrintStream originalOut = System.out;
PrintStream originalErr = System.err;

步骤 2:创建目标流

// 输出到文件
PrintStream fileOut = new PrintStream(new FileOutputStream("app.log"));

// 输入来自字符串
String input = "username\npassword123";
InputStream mockIn = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));

步骤 3:执行重定向

System.setOut(fileOut);
System.setIn(mockIn);

步骤 4:执行业务逻辑

Scanner scanner = new Scanner(System.in);
String user = scanner.nextLine();
String pass = scanner.nextLine();

System.out.println("Login attempt: " + user);

步骤 5:恢复原始流(必须)

// 先关闭重定向流
fileOut.close();

// 恢复
System.setOut(originalOut);
System.setIn(originalIn);

步骤 6:异常安全(生产级写法)

PrintStream originalOut = System.out;
PrintStream tempOut = null;
try {
    tempOut = new PrintStream(new FileOutputStream("temp.log"));
    System.setOut(tempOut);
    
    // 业务代码
    System.out.println("Captured log");
    
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (tempOut != null) {
        tempOut.close();
    }
    System.setOut(originalOut); // 恢复
}

✅ 操作 2:获取系统时间(高精度计时)

步骤 1:选择合适方法

  • System.currentTimeMillis():毫秒级,适合日志、时间戳
  • System.nanoTime():纳秒级,适合性能测试

步骤 2:执行计时

long start = System.nanoTime();

// 执行耗时操作
int sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;
}

long end = System.nanoTime();
long durationNs = end - start;
double durationMs = durationNs / 1_000_000.0;

System.out.println("耗时: " + durationMs + " ms");

⚠️ nanoTime() 不代表真实时间,仅用于计算时间差。


✅ 操作 3:高效数组拷贝(arraycopy

步骤 1:准备源数组和目标数组

int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];

步骤 2:调用 arraycopy

/*
 * 参数说明:
 * src: 源数组
 * srcPos: 源数组起始位置
 * dest: 目标数组
 * destPos: 目标数组起始位置
 * length: 拷贝长度
 */
System.arraycopy(src, 0, dest, 0, src.length);

步骤 3:验证结果

System.out.println(Arrays.toString(dest)); // [1, 2, 3, 4, 5]

✅ 比 Arrays.copyOf() 或循环拷贝快 3-5 倍(底层为 JNI 优化内存移动)。


✅ 操作 4:读取系统属性与环境变量

步骤 1:获取系统属性

// Java 版本
String javaVersion = System.getProperty("java.version");

// 用户主目录
String userHome = System.getProperty("user.home");

// 自定义属性(启动时设置)
// java -Dapp.env=prod MyApp
String env = System.getProperty("app.env", "dev"); // 默认 dev

步骤 2:获取环境变量

// 系统环境变量
String path = System.getenv("PATH");
String os = System.getenv("OS"); // Windows/Linux

// 所有环境变量
Map<String, String> envs = System.getenv();
envs.forEach((k, v) -> System.out.println(k + "=" + v));

✅ 操作 5:正常退出 JVM(exit

步骤 1:执行清理

// 关闭资源
databaseConnection.close();
fileWriter.flush();

步骤 2:调用 exit

// 0 表示正常退出
System.exit(0);

// 非 0 表示异常退出
// System.exit(1);

⚠️ exit() 会触发 ShutdownHook,而 halt() 不会。


三、常见错误

错误 原因 解决方案
NullPointerException in setXxx() 传入 null 检查流对象是否创建成功
文件未关闭导致锁死 忘记 close() 使用 try-with-resourcesfinally
arraycopy 数组越界 索引或长度超出范围 检查 srcPos + length <= src.length
exit() 后资源未释放 未注册 ShutdownHook 注册钩子或手动清理
identityHashCode 误用于集合 哈希码可能重复 使用 equals/hashCode
gc() 被频繁调用 误以为能优化性能 移除调用,优化对象创建

四、注意事项

  1. 全局性setIn/out/err 影响整个 JVM,多模块应用中需谨慎。
  2. 线程安全System 类方法是线程安全的,但重定向流可能引发竞争。
  3. 性能敏感操作
    • arraycopy:极快,推荐用于大数组拷贝
    • gc():慢,避免调用
    • nanoTime():高精度,适合微基准测试
  4. 编码问题:重定向 I/O 时注意字符编码(建议显式指定 UTF-8)。
  5. null 安全
    • identityHashCode(null)0
    • getProperty(null)NullPointerException
    • getenv(null)NullPointerException

五、使用技巧

✅ 技巧 1:测试中模拟输入

@BeforeEach
void setUp() {
    System.setIn(new ByteArrayInputStream("test\n123".getBytes()));
}

@AfterEach
void tearDown() {
    System.setIn(System.in);
}

✅ 技巧 2:捕获日志用于断言

ByteArrayOutputStream outContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));

// 执行方法
myService.process();

assertTrue(outContent.toString().contains("Success"));

✅ 技巧 3:高性能数组操作

// 扩容时使用 arraycopy
int[] newArray = new int[oldArray.length * 2];
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);

✅ 技巧 4:跨平台路径处理

String path = System.getProperty("user.home") + 
              System.getProperty("file.separator") + 
              "config.txt";

六、最佳实践

✅ 推荐做法:

场景 最佳实践
I/O 重定向 仅用于测试/调试,保存并恢复原始流
GC 调用 禁止在生产代码中使用 System.gc()
时间测量 使用 System.nanoTime() 计算耗时
数组拷贝 大数组用 System.arraycopy(),小数组用 Arrays.copyOf()
系统信息 缓存 getProperty() 结果,避免重复调用
退出程序 正常流程返回,异常时 System.exit(1)
对象标识 调试用 identityHashCode(),业务用 equals()

❌ 避免做法:

  • 在循环中调用 System.gc()
  • identityHashCode 作为 Map 的 key 长期存储
  • 忽略 arraycopy 的边界检查
  • 在 Web 请求中重定向 System.out

七、性能优化

操作 优化建议
arraycopy ✅ 已为 JNI 优化,无需额外优化
currentTimeMillis ⚠️ 高并发下可能成为瓶颈(系统调用),可用 LongAdder + 轮询更新时间
getProperty / getenv ✅ 缓存结果,避免重复调用
identityHashCode ✅ 性能极高,可频繁调用
setOut 到文件 ✅ 使用 BufferedOutputStream 包装提升性能
gc() ❌ 移除调用,优化堆大小和 GC 策略
// 优化示例:缓存系统属性
public class Config {
    private static final String ENV = 
        System.getProperty("app.env", "dev");
    
    public static String getEnv() {
        return ENV; // 避免重复 getProperty
    }
}

八、总结

项目 说明
核心价值 JVM 与 Java 程序之间的系统级接口
关键方法 arraycopy, nanoTime, setXxx, getProperty, exit
性能特征 多数方法为 JNI 实现,性能极高
使用原则 最小化使用,仅在必要时调用
风险控制 重定向和 exit 需谨慎,避免全局污染
调试利器 identityHashCode, setOut 捕获输出