深入理解System.currentTimeMillis()
  1. currentTimeMillis
    1. Linux
    2. Windows
  2. 备注

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代码实现,正如其声明:

/**
     * 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中:

/* 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,找到对应实现:

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

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

Linux

Linux的javaTimeMillis()实现:

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()实现:

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++里的宏定义等.