【1.JDK底层源码剖析】5.1 System常用方法的源码剖析

好的,我们来对
java.lang.System
类中几个最核心、最常用的方法进行深入的源码剖析。这些方法的真正实现并不在 Java 代码中,而是在本地方法(Native Methods)中,我们需要深入到 OpenJDK 的 C/C++ 源码和 JVM 运行时中去探寻。

我们将重点分析以下几个方法:


currentTimeMillis()

nanoTime()

arraycopy()

gc()


1.
public static native long currentTimeMillis();

Java 层声明


System.java
中,它只是一个简单的本地方法声明:


public static native long currentTimeMillis();
底层源码剖析 (基于 OpenJDK/HotSpot JVM)

它的实现在
jdk/src/share/native/java/lang/System.c
中,这里注册了 Java 本地方法到 C 函数的映射:


// jdk/src/share/native/java/lang/System.c
JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

// 映射关系:Java的currentTimeMillis方法 对应 C的JVM_CurrentTimeMillis函数
static JNINativeMethod methods[] = {
    {"currentTimeMillis", "()J", (void *)&JVM_CurrentTimeMillis},
    // ... 其他方法映射
};

真正的逻辑在
JVM_CurrentTimeMillis
函数中,它在
hotspot/src/share/vm/prims/jvm.cpp
里实现:


// hotspot/src/share/vm/prims/jvm.cpp
JVM_LEAF(jlong, JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored))
  JVMWrapper("JVM_CurrentTimeMillis");
  return os::javaTimeMillis();
JVM_END

继续深入,
os::javaTimeMillis()
是一个操作系统相关的函数。我们以 Linux 实现为例(在
hotspot/src/os/linux/vm/os_linux.cpp
):


// hotspot/src/os/linux/vm/os_linux.cpp
jlong os::javaTimeMillis() {
  timeval time;
  // 调用系统调用 gettimeofday(2),获取当前时间和时区信息
  int status = gettimeofday(&time, NULL);
  assert(status != -1, "linux error");
  // 将秒和微秒转换为毫秒并返回
  return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000);
}
核心要点:

本质:调用操作系统的
gettimeofday()

clock_gettime()
系统调用。精度毫秒级,但其实际精度取决于操作系统和硬件。通常不是纳秒精度。用途:适用于获取日历时间(Wall Clock Time),即“当前时刻”。适合计算日期、时间戳等,但不适合测量精确的时间间隔,因为它会受到系统时间调整(如 NTP 同步)的影响。


2.
public static native long nanoTime();

Java 层声明

public static native long nanoTime();
底层源码剖析

同样,它的映射在
System.c
中:


{"nanoTime", "()J", (void *)&JVM_NanoTime},

实现在
jvm.cpp


JVM_LEAF(jlong, JVM_NanoTime(JNIEnv *env, jclass ignored))
  JVMWrapper("JVM_NanoTime");
  return os::javaTimeNanos();
JVM_END

Linux 下的实现 (
os_linux.cpp
) 更为复杂,因为它追求高精度和单调性:


jlong os::javaTimeNanos() {
  if (os::supports_monotonic_clock()) { // 检查是否支持单调时钟
    struct timespec tp;
    // 使用 CLOCK_MONOTONIC 时钟源
    int status = os::Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
    assert(status == 0, "gettime error");
    // 将秒和纳秒转换为一个长整型数值
    jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
    return result;
  } else {
    // fallback: 如果不支持单调时钟,则使用 gettimeofday (不推荐)
    timeval time;
    int status = gettimeofday(&time, NULL);
    assert(status != -1, "linux error");
    jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
    return 1000 * usecs;
  }
}
核心要点:

本质:优先使用操作系统的单调时钟 (如
CLOCK_MONOTONIC
)。单调性:这是它与
currentTimeMillis
最关键的区别。单调时钟保证时间是线性递增的,绝不会因为系统时间的调整(如调校、夏令时)而回退或跳变。精度纳秒级,提供更高的分辨率。用途非常适合测量精确的时间间隔,如代码性能分析、超时计算、游戏循环等。绝对不要用它来表示当前时间,因为它只是一个相对起点(通常是系统启动时间)的计数器。


3.
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

这是一个极其关键的方法,Java 集合类的很多操作都依赖于它。

底层源码剖析

它的映射是:


{"arraycopy", "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},

实现在
jvm.cpp

JVM_ArrayCopy
函数中。这个函数非常长,但它做的主要工作是:

有效性检查:检查源数组和目标数组是否为
null
类型检查:检查它们是否是真正的数组,以及目标数组的类型是否与源数组类型兼容(例如,不能将
Object[]
拷贝到
String[]
,除非每个元素都是
String
)。范围检查:检查
srcPos
,
destPos
,
length
是否在各自数组的范围内。

如果所有检查都通过,它会调用
Copy::conjoint_arraycopy
这个底层函数(在
hotspot/src/share/vm/oops/oop.inline.hpp
和相关的
.cpp
文件中)。


// 这是一个非常底层的通用拷贝函数
void Copy::conjoint_arraycopy(void* src, void* dst, size_t length) {
  // ... 根据数组元素的类型和大小,选择最优的拷贝策略
  // 1. 如果长度很小,可能使用简单的循环拷贝。
  // 2. 对于大的字节数组,最终会调用平台相关的汇编代码 (如 *_copy.inc 文件)
  //    例如,在 x86 架构上,可能会使用 REP MOVSB 这样的指令进行高效的内存块复制。
}
核心要点:

高效性:JVM 会使用平台最优化的底层内存拷贝例程(通常是高度优化的汇编代码),其性能远优于用 Java 写的
for
循环。安全性:在拷贝前进行了严格的类型和边界检查,如果检查失败会抛出
ArrayStoreException

IndexOutOfBoundsException
重叠处理:它能正确处理源区域和目标区域重叠的情况(conjoint)。


4.
public static native void gc();

Java 层声明

public static native void gc();
底层源码剖析

映射关系:


{"gc", "()V", (void *)&JVM_GC},

实现在
jvm.cpp


JVM_ENTRY(void, JVM_GC(void))
  JVMWrapper("JVM_GC");
  // 如果未显式禁用GC(-XX:+DisableExplicitGC),则执行GC
  if (!DisableExplicitGC) {
    Universe::heap()->collect(GCCause::_java_lang_system_gc);
  }
JVM_END

这里的关键是
Universe::heap()->collect(...)

Universe::heap()
返回的是 JVM 当前使用的垃圾收集器(如 G1、Parallel、ZGC 等)。
GCCause::_java_lang_system_gc
是一个“原因”标识符,表示这次 GC 是由
System.gc()
调用触发的。

不同的垃圾收集器对这个调用有不同的响应。例如,对于 G1GC 的
collect
方法(在
g1CollectedHeap.cpp
),它看到这个 cause 后,通常会执行一次 Full GC


// hotspot/src/share/vm/gc_implementation/g1/g1CollectedHeap.cpp
void G1CollectedHeap::collect(GCCause::Cause cause) {
  // ... 
  if (should_do_concurrent_full_gc(cause)) {
    // ... 可能进行并发收集
  } else {
    // 对于 System.gc(),通常不会进入并发模式,而是会进入这里
    VM_G1CollectFull op(gc_count_before, full_gc_count_before, cause);
    VMThread::execute(&op); // 在VM线程中执行一次Stop-The-World的Full GC
  }
}
核心要点:

“建议”而非“命令”
System.gc()
只是向 JVM 发起一次垃圾回收的建议,JVM 可以自由选择忽略它(例如,使用了参数
-XX:+DisableExplicitGC
)。性能影响:默认情况下,它会触发一次 Stop-The-World (STW) 的 Full GC,这会暂停所有应用线程,对性能影响很大。最佳实践:在绝大多数情况下,应避免在应用代码中调用
System.gc()
。JVM 的自动垃圾收集策略通常更优。它的主要用途是在调试、性能分析或与本地代码交互的特殊场景中。

总结

方法 底层实现核心 关键特性与用途

currentTimeMillis()
系统调用
gettimeofday
/
clock_gettime
获取日历时间,精度毫秒,受系统时间调整影响。用于时间戳。

nanoTime()
系统调用
clock_gettime(CLOCK_MONOTONIC)
获取单调时间,精度纳秒,不受系统时间调整影响。用于测量时间间隔。

arraycopy()
平台相关的优化汇编指令 (如
REP MOVSB
)
高效、安全的内存块拷贝。是很多集合类操作的基础。

gc()
触发
Universe::heap()->collect()
,通常导致 Full GC
向 JVM 发起 Full GC 建议,通常应避免使用。

通过剖析底层源码,我们可以更深刻地理解这些常用方法的行为和代价,从而在开发中做出更明智的选择。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
心的头像 - 鹿快
评论 抢沙发

请登录后发表评论

    暂无评论内容