System / Runtime 系统类源码
概述
java.lang.System 和 java.lang.Runtime 是 Java 标准库中最基础的两个系统级类。System 提供标准的输入/输出流、系统属性访问、数组拷贝、GC 建议等静态工具方法;Runtime 则代表 Java 应用程序的运行时环境,提供内存查询、处理器数量获取、关闭钩子注册、进程执行等能力。二者紧密关联:System.gc() 委托给 Runtime.getRuntime().gc(),System.exit() 委托给 Runtime.getRuntime().exit()。
本文将深入拆解这两个类中的关键机制,重点关注 native 实现的底层逻辑、JVM 行为细节以及跨平台差异。
本文基于 OpenJDK 21 源码分析。
1. System.out / in / err 的初始化
1.1 声明方式
// java.lang.System(部分源码)
public static final InputStream in;
public static final PrintStream out;
public static final PrintStream err;这三个字段被声明为 public static final,但它们的初始化并不在声明时完成,而是在 System 类初始化阶段通过 initializeSystemClass() 方法完成。
1.2 initializeSystemClass() 初始化流程
// java.lang.System
private static void initializeSystemClass() {
// 1. 注册 native 方法
// 2. 通过 FileDescriptor 创建标准 I/O 流
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
// 3. 包装为 BufferedInputStream 和 PrintStream
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
// 4. 加载系统属性
// 5. 加载 System 包级别安全策略
// 6. 初始化 Console
}对应的 FileDescriptor 定义:
// java.io.FileDescriptor
public static final FileDescriptor in = new FileDescriptor(0); // 标准输入 fd=0
public static final FileDescriptor out = new FileDescriptor(1); // 标准输出 fd=1
public static final FileDescriptor err = new FileDescriptor(2); // 标准错误 fd=2文件描述符说明:
| 流 | 字段 | 文件描述符 | 对应设备 |
|---|---|---|---|
| 标准输入 | System.in | fd=0 | 键盘 / 重定向输入 |
| 标准输出 | System.out | fd=1 | 控制台 / 重定向输出 |
| 标准错误 | System.err | fd=2 | 控制台(不受重定向影响) |
1.3 setOut0() / setIn0() / setErr0() native 重定向
// java.lang.System
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);System.setOut() / System.setIn() / System.setErr() 方法在内部调用这些 native 方法:
public static void setOut(PrintStream out) {
checkIO(); // SecurityManager 检查
setOut0(out); // native 替换标准输出流
}HotSpot 层面的实现本质上是替换 JVM 内部持有的 System.in / System.out / System.err 字段引用:
// hotspot/share/prims/jvm.cpp
JVM_LEAF(void, JVM_SetIn(JNIEnv *env, jclass cls, jobject stream))
JVMWrapper("JVM_SetIn");
// 直接修改 System.in 字段的值
java_lang_System::set_in(env, stream);
JVM_END2. System.arraycopy() 的 native 实现
// java.lang.System
@IntrinsicCandidate
public static native void arraycopy(
Object src, int srcPos,
Object dest, int destPos,
int length
);arraycopy() 是 Java 中最核心的数组操作之一,它的 native 实现跨越了 JNI、JVM 运行时和 C 标准库三个层面。
2.1 JVM 入口:JVM_ArrayCopy()
// hotspot/share/prims/jvm.cpp
JVM_ENTRY(void, JVM_ArrayCopy(JNIEnv *env, jclass ignored,
jobject src, jint src_pos,
jobject dst, jint dst_pos, jint length))
// 1. 类型检查:src 和 dest 必须是数组
if (!src->is_array() || !dst->is_array()) {
THROW(vmSymbols::java_lang_ArrayStoreException());
}
// 2. 边界检查
if (src_pos < 0 || dst_pos < 0 || length < 0 ||
src_pos + length > ArrayRef::length(src) ||
dst_pos + length > ArrayRef::length(dst)) {
THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());
}
// 3. 检查 src 和 dest 类型兼容性
// 4. 分发到具体类型
if (src->is_typeArray()) {
// 基本类型数组 → TypeArrayKlass::array_copy()
TypeArrayKlass::array_copy(src, src_pos, dst, dst_pos, length);
} else {
// 引用类型数组 → ObjArrayKlass::array_copy()
ObjArrayKlass::array_copy(src, src_pos, dst, dst_pos, length);
}
JVM_END2.2 基本类型数组 vs 引用类型数组
| 数组类型 | 处理类 | 复制方式 | 说明 |
|---|---|---|---|
int[] / byte[] 等 | TypeArrayKlass | memmove() 直接内存拷贝 | 连续内存块,无引用处理 |
Object[] 引用数组 | ObjArrayKlass | 逐元素赋值 + 写屏障 | 需要处理 GC 引用,触发写屏障 |
2.3 C 层调用:memmove() 处理重叠区域
// hotspot/share/oop/typeArrayKlass.cpp
void TypeArrayKlass::array_copy(typeArrayOop src, int src_pos,
typeArrayOop dst, int dst_pos, int length) {
// 获取元素大小
int element_size = type2aelembytes(src->type());
// 计算源和目标的内存地址
void* src_addr = (void*)((char*)src->base() + src_pos * element_size);
void* dst_addr = (void*)((char*)dst->base() + dst_pos * element_size);
// memmove 处理重叠区域(区别于 memcpy)
memmove(dst_addr, src_addr, length * element_size);
}memmove() vs memcpy():memmove() 能够正确处理源和目标内存区域重叠的情况。如果 src 和 dest 是同一个数组,且复制区域有重叠(如 arraycopy(arr, 0, arr, 2, 3)),memmove() 保证复制结果正确;而 memcpy() 不保证重叠区域的正确性。
2.4 @IntrinsicCandidate 与 JIT 优化
@IntrinsicCandidate
public static native void arraycopy(...);@IntrinsicCandidate 注解标识该方法可以被 JIT 编译器识别并替换为特定的机器码。HotSpot C2 编译器会将 arraycopy() 调用转换为:
rep movsb:针对byte[]的逐字节复制(x86 SIMD)rep movsq:针对long[]/double[]的 8 字节复制(x86 SIMD)rep movsd:针对int[]/float[]的 4 字节复制
当数组长度较小时(通常 < 16 元素),JIT 可能直接内联展开为多个寄存器操作,避免函数调用开销。
3. System.gc() 的 Runtime.getRuntime().gc()
// java.lang.System
public static void gc() {
Runtime.getRuntime().gc();
}
// java.lang.Runtime
public native void gc();System.gc() 是 Runtime.gc() 的静态包装。Runtime.gc() 是一个 native 方法,其语义是建议 JVM 执行垃圾回收——JVM 可以忽略这个建议。
3.1 HotSpot 中的 Runtime.gc() 实现
// hotspot/share/prims/jvm.cpp
JVM_ENTRY(void, JVM_GC(JNIEnv *env, jclass cls))
JVMWrapper("JVM_GC");
// 如果没有使用 DisableExplicitGC 标志,则触发 GC
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END3.2 JVM GC 参数行为
| JVM 参数 | 默认值 | System.gc() 行为 |
|---|---|---|
-XX:+DisableExplicitGC | 关闭 | System.gc() 变为空操作,完全不执行 GC |
-XX:+ExplicitGCInvokesConcurrent | 关闭(G1 可用) | G1:改为并发周期而非 Full GC,减少 STW 时间 |
| 默认(无参数) | — | 触发 Full GC,含一次 Stop-The-World 的完全回收 |
各 GC 实现的行为差异:
| GC 实现 | System.gc() 行为 |
|---|---|
| Serial / Parallel | 触发 Full GC,STW 完全回收 |
| G1 | 默认触发 Full GC(串行);-XX:+ExplicitGCInvokesConcurrent 时为并发标记 + 清理 |
| ZGC | 默认无操作(System.gc() 被忽略) |
| Shenandoah | 默认无操作;-XX:+ExplicitGCInvokesConcurrent 可启用 |
3.3 runFinalization() 的关联调用
// java.lang.System
public static void runFinalization() {
Runtime.getRuntime().runFinalization();
}
// java.lang.Runtime
public native void runFinalization();runFinalization() 与 gc() 类似,也是建议 JVM 尽可能运行已失对象的 finalize() 方法。HotSpot 的 JVM_Finalize() 实现会调用 VFTable::finalize() 遍历 Finalizer 链表。
JDK 18+ 中
finalize()已被正式标记为废弃(@Deprecated(since="9", forRemoval=true)),runFinalization()也随之不推荐使用。
4. System.nanoTime() 的 native 高精度
// java.lang.System
public static native long nanoTime();nanoTime() 返回一个高精度时间源的当前值,单位为纳秒。它主要用于测量时间间隔(elapsed time),而非获取当前时刻。
4.1 跨平台实现
| 操作系统 | 底层调用 | 精度特征 | 时钟类型 |
|---|---|---|---|
| Linux | clock_gettime(CLOCK_MONOTONIC) | 纳秒级 | 单调递增 |
| Windows | QueryPerformanceCounter() + QueryPerformanceFrequency() | 微秒~纳秒级 | 硬件 TSC 或 HPET |
| macOS | mach_absolute_time() | 纳秒级 | 单调递增 |
4.2 HotSpot 源码实现(Linux 示例)
// hotspot/src/os/linux/os_linux.cpp
jlong os::javaTimeNanos() {
struct timespec tp;
// CLOCK_MONOTONIC:系统启动以来的单调时间,不受 NTP 影响
int status = clock_gettime(CLOCK_MONOTONIC, &tp);
assert(status == 0, "clock_gettime(CLOCK_MONOTONIC) failed");
jlong result = (jlong)tp.tv_sec * (1000 * 1000 * 1000) + (jlong)tp.tv_nsec;
return result;
}4.3 精度与分辨率
尽管 nanoTime() 返回值以纳秒为单位,实际精度取决于底层硬件和操作系统:
| 环境 | 典型精度 | 说明 |
|---|---|---|
| Linux x86 + TSC | ∼10ns | 使用 CPU 时间戳计数器 |
| Windows + HPET | ∼500ns - 1µs | HPET 频率通常 10-25MHz |
| Windows + TSC | ∼100ns | 较新硬件的 TSC 同步 |
| macOS Intel | ∼42ns | mach_absolute_time() 转换后 |
Java 规范只要求 nanoTime() 的精度优于 currentTimeMillis()(1ms),实际实现中远优于这个要求。
4.4 使用规范
// 正确用法:测量时间间隔
long start = System.nanoTime();
doSomething();
long elapsed = System.nanoTime() - start;
// elapsed > 0 恒成立,但可能为 0(极短操作)
// 错误用法:不要用作单调时钟的时间戳(日志等)
long now = System.nanoTime(); // ❌ 无意义的时间戳
logger.info("Event at {}", now); // 不同 JVM 实例无法比较
// 正确用法:记录间隔
long start = System.nanoTime();
// ... 操作 ...
long duration = System.nanoTime() - start; // ✅ 纳秒级间隔5. System.currentTimeMillis() vs nanoTime()
// java.lang.System
public static native long currentTimeMillis();5.1 底层实现对比
| 特性 | currentTimeMillis() | nanoTime() |
|---|---|---|
| 底层调用(Linux) | gettimeofday() / clock_gettime(CLOCK_REALTIME) | clock_gettime(CLOCK_MONOTONIC) |
| 底层调用(Windows) | GetSystemTimeAsFileTime() | QueryPerformanceCounter() |
| 返回值含义 | 1970-01-01 以来的毫秒数 | 某个未知起点以来的纳秒数 |
| 精度 | 毫秒级(实际 ∼1-15ms 粒度) | 微秒~纳秒级 |
| 单调性 | ❌ 可能回退或跳跃 | ✅ 始终单调递增 |
| 受 NTP 影响 | ✅ 会 | ❌ 不会 |
| 受时区/夏令时影响 | ✅ 会 | ❌ 不会 |
| 跨 JVM 实例可比 | ✅ 可以 | ❌ 不可以 |
| 可用于计算"当前时间" | ✅ 可以 | ❌ 不可以 |
5.2 HotSpot 实现细节
// hotspot/src/os/linux/os_linux.cpp
// currentTimeMillis: 使用 gettimeofday
jlong os::javaTimeMillis() {
struct timeval tv;
gettimeofday(&tv, NULL); // 受 NTP 调整影响
jlong result = (jlong)tv.tv_sec * 1000 + (jlong)tv.tv_usec / 1000;
return result;
}
// nanoTime: 使用 CLOCK_MONOTONIC
jlong os::javaTimeNanos() {
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp); // 单调递增,不受 NTP 影响
jlong result = (jlong)tp.tv_sec * NANOSECS_PER_SEC + tp.tv_nsec;
return result;
}5.3 选择原则
// 场景一:需要壁钟时间 / 日志时间戳 → 使用 currentTimeMillis()
logger.info("Request processed at {}", Instant.now()); // 底层也走 currentTimeMillis()
// 场景二:性能测量 / 耗时统计 → 使用 nanoTime()
long start = System.nanoTime();
executeTask();
long costNs = System.nanoTime() - start; // 高精度间隔
// 场景三:超时计算(不受系统时间跳变影响)→ 使用 nanoTime()
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
while (System.nanoTime() < deadline) {
// 等待条件满足(不会被 NTP 同步打断)
}
// 场景四:当前时间 → 必须用 currentTimeMillis()
Date now = new Date(System.currentTimeMillis()); // ✅ 正确
Date now2 = new Date(System.nanoTime()); // ❌ 荒谬的结果5.4 currentTimeMillis() 的粒度问题
一个常见的误解是 currentTimeMillis() 返回毫秒级精度的时间。实际上,不同操作系统的实际粒度可能远大于 1ms:
Windows: ~15ms(默认时钟中断间隔)或 1ms(高分辨率定时器)
Linux: ~1ms(通常,取决于内核配置)
macOS: ~1ms(通常)这意味着连续两次调用可能返回相同的时间戳:
long t1 = System.currentTimeMillis();
long t2 = System.currentTimeMillis();
// t1 == t2 在 Windows 上很常见!6. Runtime.availableProcessors()
// java.lang.Runtime
public native int availableProcessors();6.1 跨平台实现
| 操作系统 | 底层调用 |
|---|---|
| Linux | sysconf(_SC_NPROCESSORS_ONLN) |
| Windows | GetSystemInfo() → dwNumberOfProcessors |
| macOS | sysctl("hw.ncpu") |
6.2 HotSpot 源码
// hotspot/src/os/linux/os_linux.cpp
int os::active_processor_count() {
// Linux: 读取 /sys/devices/system/cpu/online
// 或使用 sysconf(_SC_NPROCESSORS_ONLN)
int online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
// 读取 cgroup 限制
// 当前进程可能受 cgroup cpuset 限制
if (CgroupSubsystemFactory::is_active()) {
int cgroup_cpus = OSContainer::active_processor_count();
if (cgroup_cpus > 0) {
return min(online_cpus, cgroup_cpus);
}
}
return online_cpus;
}6.3 容器环境的影响
在 Docker / Kubernetes 容器中,availableProcessors() 的行为:
- JDK 8u191+:正确识别
--cpus/--cpu-shares限制,返回容器的 CPU 配额而非宿主机 CPU 数量 - JDK 8u131-8u191:默认返回宿主机 CPU 数量(需要
-XX:+UseCGroupMemoryLimitForHeap等参数) - JDK 10+:默认支持 cgroup v1 和 v2
示例场景:
宿主机:32 核
容器限制:--cpus=2
availableProcessors() 返回:2(JDK 10+ / 8u191+)6.4 应用场景
// ForkJoinPool 默认并行度
ForkJoinPool.commonPool().getParallelism();
// → Runtime.getRuntime().availableProcessors() - 1
// 线程池大小配置
int processors = Runtime.getRuntime().availableProcessors();
int poolSize = processors * (1 + targetRatio); // CPU 密集型
int poolSize = processors * (waitTime / computeTime); // IO 密集型6.5 相关内存方法
// java.lang.Runtime
public long maxMemory() { // -Xmx 指定的最大堆内存
return Runtime.maxMemory0();
}
public long totalMemory() { // 当前已申请的总堆内存(-Xms 到 -Xmx 之间)
return Runtime.totalMemory0();
}
public long freeMemory() { // 堆中剩余可用的内存
return Runtime.freeMemory();
}7. Runtime.addShutdownHook(Thread)
// java.lang.Runtime
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
public boolean removeShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
return ApplicationShutdownHooks.remove(hook);
}7.1 内部存储:ApplicationShutdownHooks
// java.lang.ApplicationShutdownHooks
class ApplicationShutdownHooks {
private static IdentityHashMap<Thread, Thread> hooks;
static {
hooks = new IdentityHashMap<>();
// 注册到 Shutdown 挂钩系统
Shutdown.add(1, // 关闭阶段优先级
new Runnable() {
public void run() {
runHooks();
}
}
);
}
static synchronized void add(Thread hook) {
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
static void runHooks() {
// 逐个启动 shutdown hook 线程(并发执行)
for (Thread hook : hooks.keySet()) {
hook.start();
}
// 等待所有 hook 执行完毕
for (Thread hook : hooks.keySet()) {
try {
hook.join();
} catch (InterruptedException e) { }
}
}
}IdentityHashMap<Thread, Thread> 使用引用相等性(==)而非 equals() 来区分线程,确保了每个 Thread 对象的唯一性。
7.2 关闭序列的执行流程
JVM 关闭触发
│
├── 最后一个非守护线程退出
├── System.exit(int) 调用
├── Ctrl+C (SIGINT)
└── kill -15 SIGTERM (Linux)
│
▼
Shutdown.beforeHalt()
│ (最终化:runAllFinalizers)
▼
Shutdown.runHooks()
│ (执行所有 ShutdownHook,并发启动所有线程)
▼
Shutdown.halt0(int status)
│ (native → _exit() 系统调用)
▼
JVM 进程退出7.3 不执行 ShutdownHook 的场景
| 场景 | 是否执行 Hook | 说明 |
|---|---|---|
| 最后一个非守护线程退出 | ✅ | 正常关闭 |
System.exit(0) | ✅ | 运行 hook 后退出 |
Ctrl+C / SIGINT | ✅ | JVM 注册了信号处理器 |
kill -15 SIGTERM | ✅ | 同上 |
kill -9 SIGKILL | ❌ | 操作系统直接终止进程 |
Runtime.halt(n) | ❌ | JVM 强制退出,跳过 hook |
| OS 崩溃 / 断电 | ❌ | 无机会执行 |
System.exit() 中 hook 超时 | ⚠️ | HotSpot 默认 hook 超时时间(可配置) |
7.4 使用示例与限制
// 正确使用方式
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown hook executing...");
// 清理临时文件、关闭资源等
}));
// 常见错误
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.exit(0); // ❌ 禁止在 hook 中调用 System.exit()
}));
Thread hook = new Thread(() -> { ... });
Runtime.getRuntime().addShutdownHook(hook);
Runtime.getRuntime().addShutdownHook(hook); // ❌ IllegalArgument: previously registered8. Shutdown.beforeHalt() 和 halt() 的退出流程
java.lang.Shutdown 是包级私有的关闭管理类,它定义了 JVM 关闭的完整序列。
8.1 Shutdown 类的核心结构
// java.lang.Shutdown(包级私有,不可直接访问)
class Shutdown {
// 关闭状态
private static final int RUNNING = 0; // 正常运行
private static final int HOOKS = 1; // 正在执行关闭钩子
private static final int FINALIZERS = 2; // 正在执行 finalizer
private static volatile int state = RUNNING;
// 关闭钩子数组(最多 10 个槽位,按优先级排列)
private static final Runnable[] hooks = new Runnable[10];
// 添加关闭钩子(由 ApplicationShutdownHooks 调用)
static void add(int slot, Runnable hook) {
hooks[slot] = hook;
}
// 触发关闭序列
static void shutdown() {
if (state > RUNNING) return; // 防止重复进入
synchronized (Lock) {
if (state > RUNNING) return;
state = HOOKS;
}
// 顺序执行所有注册的 hook
runHooks();
// 执行 finalizer
runAllFinalizers();
state = FINALIZERS;
}
// 强制退出
static native void halt0(int status);
}8.2 Runtime.exit() / System.exit() 的完整调用链
// java.lang.System
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
// java.lang.Runtime
public void exit(int status) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkExit(status); // SecurityManager 安全检查
}
Shutdown.shutdown(); // 执行关闭序列(hooks + finalizers)
Shutdown.halt0(status); // native 退出(只有此步才是真正的进程退出)
}8.3 Runtime.halt() — 强制退出
// java.lang.Runtime
public void halt(int status) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkExit(status);
}
Shutdown.beforeHalt(); // 仅运行 finalizer(已废弃)
Shutdown.halt0(status); // 直接 native 退出
}8.4 退出方式对比
| 方法 | 执行 SecurityManager 检查 | 执行 ShutdownHook | 执行 Finalizer | 底层调用 |
|---|---|---|---|---|
System.exit(n) | ✅ | ✅ | ✅ | _exit() |
Runtime.halt(n) | ✅ | ❌ | ❌(仅 beforeHalt) | _exit() |
kill -9 | ❌ | ❌ | ❌ | SIGKILL |
8.5 关闭时序图
时间轴 →
──────────────────────────────────────────────────────────
System.exit(0) 被调用
│
▼
SecurityManager.checkExit(status) ← 安全检查
│
▼
Shutdown.state = HOOKS ← 标记开始执行 hook
│
▼
Shutdown.runHooks() ← 执行所有 ShutdownHook(并发)
│ ├── Hook-1 (Thread) ──→ start/join
│ ├── Hook-2 (Thread) ──→ start/join
│ └── Hook-3 (Thread) ──→ start/join
│
▼
Shutdown.runAllFinalizers() ← 执行 finalize(如果启用)
│
▼
Shutdown.halt0(status) ← native: _exit(status)
│
▼
JVM 进程终止9. 其他重要系统方法
9.1 System.getProperty(String key) 系统属性
// java.lang.System
public static String getProperty(String key) {
checkKey(key); // null/空检查
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertyAccess(key); // 安全管理器检查
}
return props.getProperty(key); // 从 Properties 表中读取
}系统属性存储在一个 Properties 对象中,由 initializeSystemClass() 时加载。常见系统属性:
| 属性键 | 说明 | 示例值 |
|---|---|---|
java.version | JDK 版本号 | 21.0.1 |
java.home | JDK 安装目录 | C:\Program Files\Java\jdk-21 |
os.name | 操作系统名称 | Windows 11 / Linux |
user.dir | 用户当前工作目录 | D:\workspace\project |
file.separator | 文件分隔符 | \(Windows)/ /(Unix) |
line.separator | 换行符 | \r\n(Windows)/ \n(Unix) |
// 读取系统属性
String version = System.getProperty("java.version"); // "21.0.1"
String osName = System.getProperty("os.name"); // "Windows 11"
// 设置系统属性(覆盖已有值)
System.setProperty("myapp.config.dir", "/etc/myapp/");
// 清除系统属性
System.clearProperty("myapp.config.dir");
// 获取所有属性的只读视图
Properties props = System.getProperties();9.2 System.getenv() 环境变量
// java.lang.System
public static Map<String, String> getenv() {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("getenv.*"));
}
return ProcessEnvironment.getenv(); // native 方式获取
}
public static String getenv(String name) {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("getenv." + name));
}
return ProcessEnvironment.getenv(name);
}getProperty() vs getenv() 对比:
| 特性 | getProperty() | getenv() |
|---|---|---|
| 来源 | JVM 启动参数 -Dkey=value | 操作系统环境变量 |
| 可变性 | 运行时可通过 setProperty() 修改 | JVM 启动后不可修改 |
| 大小写 | 键通常小写 | Windows 不区分大小写,Unix 区分 |
| 典型用途 | JVM 配置参数 | 容器/系统级配置 |
9.3 System.identityHashCode(Object) — 原始哈希码
// java.lang.System
public static native int identityHashCode(Object x);identityHashCode() 返回对象的默认哈希码,即无论该类是否重写了 hashCode(),都返回 Object.hashCode() 原本计算的值。
// 与 Object.hashCode() 的关系
Object obj = new Object();
int h1 = obj.hashCode(); // Object 的 hashCode
int h2 = System.identityHashCode(obj); // 完全等价
// 当 hashCode 被重写时
String s = "hello";
int h3 = s.hashCode(); // String 重写的 hashCode: 99162322
int h4 = System.identityHashCode(s); // Object 的原始 hashCode(不同字符串对象不同)identityHashCode() 的典型用途:
IdentityHashMap:使用==和identityHashCode()而非equals()/hashCode()来比较键- 调试/诊断:获取对象的唯一标识(即使
hashCode()被重写) - JVM 分析工具:跟踪对象引用的唯一性
9.4 Console 类的懒加载初始化
// java.lang.System
private static volatile Console cons = null;
// 通过 System.console() 访问
public static Console console() {
if (cons == null) {
synchronized (System.class) {
if (cons == null) {
cons = Console.console(); // 懒加载初始化
}
}
}
return cons;
}Console 对象仅在运行在原生终端时可用(不是在 IDE 或重定向 I/O 中):
Console console = System.console();
if (console != null) {
String input = console.readLine("Enter password: ");
char[] password = console.readPassword("Password: ");
} else {
// 没有可用终端(如后台运行、IDE 执行)
}Console 的初始化在 initializeSystemClass() 中被触发,但实际 Console.console() 会尝试通过 native 方法 _console() 检查是否连接到真实的终端设备。
9.5 SecurityManager 的废弃(JDK 18+)
// java.lang.System
public static SecurityManager getSecurityManager() {
// JDK 18+ 默认返回 null
return security;
}SecurityManager 在 JDK 17 中被标记为废弃(@Deprecated(since="17", forRemoval=true)),JDK 18+ 默认不启用:
JDK 版本变化:
JDK 1.0: SecurityManager 引入
JDK 17: 标记为 @Deprecated(forRemoval=true)
JDK 18+: getSecurityManager() 默认返回 null替代方案:Java 平台的细粒度安全控制正在向以下方向演进:
SecurityManager→ 标准Permission类 +AccessController- 更现代的方案:模块化系统(JPMS) 的模块边界控制
10. 关键类结构一览
java.lang.System(工具类,final,私有构造器)
├── 标准 I/O
│ ├── in: InputStream ← FileDescriptor.in (fd=0)
│ ├── out: PrintStream ← FileDescriptor.out (fd=1)
│ ├── err: PrintStream ← FileDescriptor.err (fd=2)
│ └── setIn0/setOut0/setErr0() ← native 重定向
├── 数组操作
│ └── arraycopy(Object, int, Object, int, int) ← @IntrinsicCandidate native
├── 内存管理
│ ├── gc() ← Runtime.getRuntime().gc()
│ └── runFinalization() ← Runtime.getRuntime().runFinalization()
├── 时间
│ ├── currentTimeMillis() ← gettimeofday / GetSystemTimeAsFileTime
│ └── nanoTime() ← CLOCK_MONOTONIC / QueryPerformanceCounter
├── 属性/环境
│ ├── getProperty(String) ← Properties 表
│ ├── setProperty/clearProperty ← 修改系统属性
│ ├── getenv() ← ProcessEnvironment
│ └── getProperties() ← 完整属性表
├── 标识
│ └── identityHashCode(Object) ← Object 的原始 hashCode(不被重写影响)
├── 控制台
│ └── console() ← 懒加载 Console 对象
└── 安全(已废弃)
└── getSecurityManager() ← JDK 18+ 默认 null
java.lang.Runtime(单例,每个 JVM 一个实例)
├── 当前运行时
│ └── getRuntime() ← 静态单例
├── GC 控制
│ ├── gc() ← native,建议 GC
│ └── runFinalization() ← native,建议 finalize
├── 内存查询
│ ├── maxMemory() ← -Xmx 值
│ ├── totalMemory() ← 当前已申请堆
│ └── freeMemory() ← 当前可用堆
├── 处理器信息
│ └── availableProcessors() ← sysconf / GetSystemInfo
├── JVM 关闭
│ ├── exit(int) ← Shutdown.shutdown() + halt0()
│ ├── halt(int) ← 直接 halt0(),跳过 hook
│ ├── addShutdownHook(Thread) ← ApplicationShutdownHooks
│ └── removeShutdownHook(Thread) ← 注销 hook
├── 进程执行
│ └── exec(String/...) ← ProcessBuilder 创建子进程
└── 版本信息
└── version() ← Runtime.Version(JDK 9+)
java.lang.Shutdown(包级私有,final 类)
├── 状态
│ ├── RUNNING (0)
│ ├── HOOKS (1)
│ └── FINALIZERS (2)
├── shutdown() ← 触发完整关闭序列
├── runHooks() ← 顺序执行所有注册的 hook
├── runAllFinalizers() ← 运行 finalizer
└── halt0(int) ← native: _exit() 系统调用
java.lang.ApplicationShutdownHooks(包级私有)
├── hooks: IdentityHashMap<Thread, Thread> ← 引用相等性存储
├── add(Thread) ← 注册 hook
├── remove(Thread) ← 注销 hook
└── runHooks() ← 并发启动 → join 等待总结
System是纯静态工具类:提供标准 I/O(out/in/err的 native 初始化与重定向)、数组拷贝(arraycopy的@IntrinsicCandidateJIT 优化)、系统属性/环境变量访问、以及高精度时间等高复用功能Runtime是运行时的单例代理:gc()、exit()、halt()等核心生命周期方法均由Runtime提供,System只是静态包装arraycopy从 Java → JVM → C 的链路:@IntrinsicCandidate标注后可被 JIT 识别为rep movs等 SIMD 指令,memmove()支持重叠区域复制,ObjArrayKlass额外维护 GC 写屏障currentTimeMillis()与nanoTime()有本质区别:前者是壁钟时间(受 NTP 影响),后者是单调递增时间(仅适合测量间隔);选择原则取决于"需要时间点"还是"需要时间差"addShutdownHook提供优雅关闭能力:JVM 关闭序列为SecurityManager.checkExit → Shutdown.runHooks() → runFinalizers() → halt0(_exit()),Runtime.halt()可跳过 hook 实现强制退出- 容器感知能力持续演进:
availableProcessors()和maxMemory()在 JDK 8u191+/JDK 10+ 中正确识别 cgroup 限制,对容器化部署至关重要 SecurityManager已走向废弃:JDK 18+ 默认不启用,Java 平台安全控制正转向模块系统和细粒度权限模型