深入理解System.currentTimeMillis()

currentTimeMillis

当我们在Java需要获取当系统当前时间时,想到的第一个函数肯定是System.currentTimeMills(),这个函数返回从1970年1月1日凌晨0点(准确地说是1970-01-01 00:00:00 UTC)到当前时刻所走过的时间,正如其名字所表示,这个函数返回的时间以毫秒为单位.

System.currentTimeMills()的返回值取决于Java运行时系统的本地时区!千万不要忘记这一点!
同一时刻,在英国和中国的两个人同时用System.currentTimeMills()获取当前系统时间,其返回值不是一样的,除非手动将操作系统的时区设置成同一个时区(英国使用UTC+0,而中国使用UTC+8,中国比英国快8个小时).

System.currentTimeMills()依赖于OS的实现,所以这个函数并不是由java代码实现,正如其声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Returns the current time in milliseconds. Note that
* while the unit of time of the return value is a millisecond,
* the granularity of the value depends on the underlying
* operating system and may be larger. For example, many
* operating systems measure time in units of tens of
* milliseconds.
*
* <p> See the description of the class {@code Date} for
* a discussion of slight discrepancies that may arise between
* "computer time" and coordinated universal time (UTC).
*
* @return the difference, measured in milliseconds, between
* the current time and midnight, January 1, 1970 UTC.
* @see java.util.Date
*/
@HotSpotIntrinsicCandidate
public static native long currentTimeMillis();

Java用native关键词表示这个函数由本地函数实现.那就让我们看看System.currentTimeMills到底是如何实现的.接下来查看Windows和Linux平台实现(OS类别实在太多,你有兴趣可以查看其他OS平台下的实现).

首先,Java中的System类里的native方法,对应到Java的实现源码就在System.c中:

1
2
3
4
5
6
/* Only register the performance-critical methods */static JNINativeMethod methods[] = {
{"currentTimeMillis", "()J", (void *)&JVM_CurrentTimeMillis},
{"nanoTime", "()J", (void *)&JVM_NanoTime},
{"arraycopy", "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},
};

这里给出了java中的函数名字,和对应的c函数指针,通过c函数指针JVM_CurrentTimeMillis,找到对应实现:

1
2
3
4
JVM_LEAF(jlong, JVM_CurrentTimeMillis(JNIEnv *env, jclass ignored))
JVMWrapper("JVM_CurrentTimeMillis");
return os::javaTimeMillis();
JVM_END

由上述源码可知,调用各个OS的os::javaTimeMillis()来获取当前时间.

Linux

Linux的javaTimeMillis()实现:

1
2
3
4
5
6
jlong os::javaTimeMillis() {
timeval time;
int status = gettimeofday(&time, NULL);
assert(status != -1, "linux error");
return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000);
}

由第3行代码可知,在Linux平台下Java获取系统当前时间实际调用的就是函数gettimeofday(),返回的结果放入time中.第5行代码通过单位的换算,返回以毫秒为单位的时间.

gettimeofday返回的时间以秒和微秒为单位,但是这并不意味着其时间的准确度就是1个微秒,其准确度取决于你使用的机器(关于gettimeofday详细介绍可以戳这里,这里就不再过多说明).

Windows

Windows的javaTimeMillis()实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jlong os::javaTimeMillis() {           
if (UseFakeTimers) {
return fake_time++;
} else {
FILETIME wt;
GetSystemTimeAsFileTime(&wt);// Windows平台提供的获取当前时间的函数
return windows_to_java_time(wt);// 转换成毫秒,并返回
}
}

jlong windows_to_java_time(FILETIME wt) {
// 根据wt存放的时间信息转换成jlong类型.
jlong a = jlong_from(wt.dwHighDateTime, wt.dwLowDateTime);
// 这里有两个疑问:
// 1.为什么要减去一个偏移量offset()?
// 答:因为FILETIME结构的wt代表从1601年1月1日开始到当前的时间的一个数字(时间以100纳秒为间隔得到:总时间(以纳秒为单位)除以100就得到这个数字),而Java需要的是计算从1970年1月1日开始到现在的毫秒数;那么就需要减去这个差值.
// 2.减去偏移量之后,为什么还要除以10000(一万)?
// 答:因为a-offset()的结果是以100纳秒为基本单位的一个数字,需要转换成毫秒,就需要除以10000.
// 1秒=1000毫秒;
// 1毫秒=1000微秒;
// 1微秒=1000纳秒.
return (a - offset()) / 10000;
}

第2行到第3行的代码可以先忽略,这部分逻辑是debug用的;关键在于第6行的GetSystemTimeAsFileTime()和第7行的windows_to_java_time()函数调用.同样的要注意,GetSystemTimeAsFileTime()返回的时间信息虽然以100个纳秒为间隔,但是这并不意味着其准确度就是100纳秒,这取决于你的Windows系统.

备注

1.以上所说java的版本均为java11,实现java本身的源码来自OpenJdk 11.

2.OS是operater system,即操作系统的缩写.

3.UTC是Universal Time Coordinated的缩写,即协调世界时.比如北京时间和UTC +0的时差为+8,也就是UTC+8,俗称东八区;但是因为祖国幅员辽阔,横跨多个时区,为了保证时间的准确统一,祖国其他地方统一使用北京时间授时.

4.System.c位于Java源码目录下的java.base/share/native/libjava/System.c.

5.探究java方法的实现,目的是理解其原理做到知其然知其所以然,但并不会陷入过多的实现细节,比如c/C++里的宏定义等.

返回首页