一、核心概念
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-resources 或 finally |
arraycopy 数组越界 |
索引或长度超出范围 | 检查 srcPos + length <= src.length |
exit() 后资源未释放 |
未注册 ShutdownHook |
注册钩子或手动清理 |
identityHashCode 误用于集合 |
哈希码可能重复 | 使用 equals/hashCode |
gc() 被频繁调用 |
误以为能优化性能 | 移除调用,优化对象创建 |
四、注意事项
- 全局性:
setIn/out/err
影响整个 JVM,多模块应用中需谨慎。 - 线程安全:
System
类方法是线程安全的,但重定向流可能引发竞争。 - 性能敏感操作:
arraycopy
:极快,推荐用于大数组拷贝gc()
:慢,避免调用nanoTime()
:高精度,适合微基准测试
- 编码问题:重定向 I/O 时注意字符编码(建议显式指定 UTF-8)。
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 捕获输出 |