好的,我们来对
类中几个最核心、最常用的方法进行深入的源码剖析。这些方法的真正实现并不在 Java 代码中,而是在本地方法(Native Methods)中,我们需要深入到 OpenJDK 的 C/C++ 源码和 JVM 运行时中去探寻。
java.lang.System
我们将重点分析以下几个方法:
currentTimeMillis()
nanoTime()
arraycopy()
gc()
1.
public static native long currentTimeMillis();
public static native long currentTimeMillis();
Java 层声明
在
中,它只是一个简单的本地方法声明:
System.java
public static native long currentTimeMillis();
底层源码剖析 (基于 OpenJDK/HotSpot JVM)
它的实现在
中,这里注册了 Java 本地方法到 C 函数的映射:
jdk/src/share/native/java/lang/System.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
继续深入,
是一个操作系统相关的函数。我们以 Linux 实现为例(在
os::javaTimeMillis()
):
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()
系统调用。精度:毫秒级,但其实际精度取决于操作系统和硬件。通常不是纳秒精度。用途:适用于获取日历时间(Wall Clock Time),即“当前时刻”。适合计算日期、时间戳等,但不适合测量精确的时间间隔,因为它会受到系统时间调整(如 NTP 同步)的影响。
clock_gettime()
2.
public static native long nanoTime();
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);
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
。重叠处理:它能正确处理源区域和目标区域重叠的情况(conjoint)。
IndexOutOfBoundsException
4.
public static native void gc();
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(...)
返回的是 JVM 当前使用的垃圾收集器(如 G1、Parallel、ZGC 等)。
Universe::heap()
是一个“原因”标识符,表示这次 GC 是由
GCCause::_java_lang_system_gc
调用触发的。
System.gc()
不同的垃圾收集器对这个调用有不同的响应。例如,对于 G1GC 的
方法(在
collect
),它看到这个 cause 后,通常会执行一次 Full GC。
g1CollectedHeap.cpp
// 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
}
}
核心要点:
“建议”而非“命令”:
只是向 JVM 发起一次垃圾回收的建议,JVM 可以自由选择忽略它(例如,使用了参数
System.gc()
)。性能影响:默认情况下,它会触发一次 Stop-The-World (STW) 的 Full GC,这会暂停所有应用线程,对性能影响很大。最佳实践:在绝大多数情况下,应避免在应用代码中调用
-XX:+DisableExplicitGC
。JVM 的自动垃圾收集策略通常更优。它的主要用途是在调试、性能分析或与本地代码交互的特殊场景中。
System.gc()
总结
方法 | 底层实现核心 | 关键特性与用途 |
---|---|---|
|
系统调用 /
|
获取日历时间,精度毫秒,受系统时间调整影响。用于时间戳。 |
|
系统调用
|
获取单调时间,精度纳秒,不受系统时间调整影响。用于测量时间间隔。 |
|
平台相关的优化汇编指令 (如 ) |
高效、安全的内存块拷贝。是很多集合类操作的基础。 |
|
触发 ,通常导致 Full GC |
向 JVM 发起 Full GC 建议,通常应避免使用。 |
通过剖析底层源码,我们可以更深刻地理解这些常用方法的行为和代价,从而在开发中做出更明智的选择。
暂无评论内容