JVM 源码分析之 System.currentTimeMillis 及 nanoTime 原理详解

概述

上周@望陶问了我一个现象很诡异的问题,说JDK7和JDK8下的System.nanoTime()输出完全不一样,而且差距还非常大,是不是两个版本里的实现不一样,之前我也没注意过这个细节,觉得非常奇怪,于是自己也在本地mac机器上马上测试了一下,得到如下输出:

~/Documents/workspace/Test/src ᐅ /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/bin/java NanosTest 1480265318432558000 ~/Documents/workspace/Test/src ᐅ /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/bin/java NanosTest 1188453233877 

还真不一样,于是我再到linux下跑了一把,发现两个版本下的值基本上差不多的,也就是主要是mac下的实现可能不一样

于是我又调用System.currentTimeMillis(),发现其输出结果和System.nanoTime()也完全不是1000000倍的比例

~/Documents/workspace/Test/src ᐅ /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/bin/java NanosTest 1563115443175 1480265707257 

另外System.nanoTime()输出的到底是什么东西,这个数字好奇怪

这三个小细节平时没有留意,好奇心作祟,于是马上想一查究竟

再列下主要想理清楚的三个问题

  • 在mac下发现System.nanoTime()在JDK7和JDK8下输出的值怎么完全不一样
  • System.nanoTime()的值很奇怪,究竟是怎么算出来的
  • System.currentTimeMillis()为何不是System.nanoTime()的1000000倍

MAC不同JDK版本下nanoTime实现异同

在mac下,首先看JDK7的nanoTime实现

jlong os::javaTimeNanos() { if (Bsd::supports_monotonic_clock()) { struct timespec tp; int status = Bsd::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 { timeval time; int status = gettimeofday(&time, NULL); assert(status != -1, "bsd error"); jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec); return 1000 * usecs; } } 

再来看JDK8下的实现

#ifdef __APPLE__ jlong os::javaTimeNanos() { const uint64_t tm = mach_absolute_time(); const uint64_t now = (tm * Bsd::_timebase_info.numer) / Bsd::_timebase_info.denom; const uint64_t prev = Bsd::_max_abstime; if (now <= prev) { return prev; // same or retrograde time; } const uint64_t obsv = Atomic::cmpxchg(now, (volatile jlong*)&Bsd::_max_abstime, prev); assert(obsv >= prev, "invariant"); // Monotonicity // If the CAS succeeded then we're done and return "now". // If the CAS failed and the observed value "obsv" is >= now then // we should return "obsv". If the CAS failed and now > obsv > prv then // some other thread raced this thread and installed a new value, in which case // we could either (a) retry the entire operation, (b) retry trying to install now // or (c) just return obsv. We use (c). No loop is required although in some cases // we might discard a higher "now" value in deference to a slightly lower but freshly // installed obsv value. That's entirely benign -- it admits no new orderings compared // to (a) or (b) -- and greatly reduces coherence traffic. // We might also condition (c) on the magnitude of the delta between obsv and now. // Avoiding excessive CAS operations to hot RW locations is critical. // See https://blogs.oracle.com/dave/entry/cas_and_cache_trivia_invalidate return (prev == obsv) ? now : obsv; } #else // __APPLE__ 

果然发现JDK8下多了一个__APPLE__宏下定义的实现,和JDK7及之前的版本的实现是不一样的,不过其他BSD系统是一样的,只是macos有点不一样,因为平时咱们主要使用的环境还是Linux为主,因此对于macos下具体异同就不做过多解释了,有兴趣的自己去研究一下。

Linux下nanoTime的实现

在linux下JDK7和JDK8的实现都是一样的

jlong os::javaTimeNanos() { if (Linux::supports_monotonic_clock()) { struct timespec tp; int status = 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 { 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; } } 

Linux::supports_monotonic_clock决定了走哪个具体的分支

static inline bool supports_monotonic_clock() { return _clock_gettime != NULL; } 

_clock_gettime的定义在

void os::Linux::clock_init() { // we do dlopen's in this particular order due to bug in linux // dynamical loader (see 6348968) leading to crash on exit void* handle = dlopen("librt.so.1", RTLD_LAZY); if (handle == NULL) { handle = dlopen("librt.so", RTLD_LAZY); } if (handle) { int (*clock_getres_func)(clockid_t, struct timespec*) = (int(*)(clockid_t, struct timespec*))dlsym(handle, "clock_getres"); int (*clock_gettime_func)(clockid_t, struct timespec*) = (int(*)(clockid_t, struct timespec*))dlsym(handle, "clock_gettime"); if (clock_getres_func && clock_gettime_func) { // See if monotonic clock is supported by the kernel. Note that some // early implementations simply return kernel jiffies (updated every // 1/100 or 1/1000 second). It would be bad to use such a low res clock // for nano time (though the monotonic property is still nice to have). // It's fixed in newer kernels, however clock_getres() still returns // 1/HZ. We check if clock_getres() works, but will ignore its reported // resolution for now. Hopefully as people move to new kernels, this // won't be a problem. struct timespec res; struct timespec tp; if (clock_getres_func (CLOCK_MONOTONIC, &res) == 0 && clock_gettime_func(CLOCK_MONOTONIC, &tp) == 0) { // yes, monotonic clock is supported _clock_gettime = clock_gettime_func; return; } else { // close librt if there is no monotonic clock dlclose(handle); } } } warning("No monotonic clock was available - timed services may " \ "be adversely affected if the time-of-day clock changes"); } 

说白了,其实就是看librt.so.1或者librt.so中是否定义了clock_gettime函数,如果定义了,就直接调用这个函数来获取时间,注意下上面的传给clock_gettime的一个参数是CLOCK_MONOTONIC,至于这个参数的作用后面会说,这个函数在glibc中有定义

/* Get current value of CLOCK and store it in TP. */ int __clock_gettime (clockid_t clock_id, struct timespec *tp) { int retval = -1; switch (clock_id) { #ifdef SYSDEP_GETTIME SYSDEP_GETTIME; #endif #ifndef HANDLED_REALTIME case CLOCK_REALTIME: { struct timeval tv; retval = gettimeofday (&tv, NULL); if (retval == 0) TIMEVAL_TO_TIMESPEC (&tv, tp); } break; #endif default: #ifdef SYSDEP_GETTIME_CPU SYSDEP_GETTIME_CPU (clock_id, tp); #endif #if HP_TIMING_AVAIL if ((clock_id & ((1 << CLOCK_IDFIELD_SIZE) - 1)) == CLOCK_THREAD_CPUTIME_ID) retval = hp_timing_gettime (clock_id, tp); else #endif __set_errno (EINVAL); break; #if HP_TIMING_AVAIL && !defined HANDLED_CPUTIME case CLOCK_PROCESS_CPUTIME_ID: retval = hp_timing_gettime (clock_id, tp); break; #endif } return retval; } weak_alias (__clock_gettime, clock_gettime) libc_hidden_def (__clock_gettime) 

而对应的宏SYSDEP_GETTIME定义如下:

#define SYSDEP_GETTIME \ SYSDEP_GETTIME_CPUTIME; \ case CLOCK_REALTIME: \ case CLOCK_MONOTONIC: \ retval = INLINE_VSYSCALL (clock_gettime, 2, clock_id, tp); \ break /* We handled the REALTIME clock here. */ #define HANDLED_REALTIME 1 #define HANDLED_CPUTIME 1 #define SYSDEP_GETTIME_CPU(clock_id, tp) \ retval = INLINE_VSYSCALL (clock_gettime, 2, clock_id, tp); \ break #define SYSDEP_GETTIME_CPUTIME /* Default catches them too. */ 

最终是调用的clock_gettime系统调用:

int clock_gettime(clockid_t, struct timespec *) __attribute__((weak, alias("__vdso_clock_gettime"))); notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts) { if (likely(gtod->sysctl_enabled)) switch (clock) { case CLOCK_REALTIME: if (likely(gtod->clock.vread)) return do_realtime(ts); break; case CLOCK_MONOTONIC: if (likely(gtod->clock.vread)) return do_monotonic(ts); break; case CLOCK_REALTIME_COARSE: return do_realtime_coarse(ts); case CLOCK_MONOTONIC_COARSE: return do_monotonic_coarse(ts); } return vdso_fallback_gettime(clock, ts); } 

而我们JVM里取纳秒数时传入的是CLOCK_MONOTONIC这个参数,因此会调用如下的方法

notrace static noinline int do_monotonic(struct timespec *ts) { unsigned long seq, ns, secs; do { seq = read_seqbegin(&gtod->lock); secs = gtod->wall_time_sec; ns = gtod->wall_time_nsec + vgetns(); secs += gtod->wall_to_monotonic.tv_sec; ns += gtod->wall_to_monotonic.tv_nsec; } while (unlikely(read_seqretry(&gtod->lock, seq))); vset_normalized_timespec(ts, secs, ns); return 0; } 

上面的wall_to_monotonictv_sec以及tv_nsec都是负数,在系统启动初始化的时候设置,记录了启动的时间

void __init timekeeping_init(void) { struct clocksource *clock; unsigned long flags; struct timespec now, boot; read_persistent_clock(&now); read_boot_clock(&boot); write_seqlock_irqsave(&xtime_lock, flags); ntp_init(); clock = clocksource_default_clock(); if (clock->enable) clock->enable(clock); timekeeper_setup_internals(clock); xtime.tv_sec = now.tv_sec; xtime.tv_nsec = now.tv_nsec; raw_time.tv_sec = 0; raw_time.tv_nsec = 0; if (boot.tv_sec == 0 && boot.tv_nsec == 0) { boot.tv_sec = xtime.tv_sec; boot.tv_nsec = xtime.tv_nsec; } set_normalized_timespec(&wall_to_monotonic, -boot.tv_sec, -boot.tv_nsec); total_sleep_time.tv_sec = 0; total_sleep_time.tv_nsec = 0; write_sequnlock_irqrestore(&xtime_lock, flags); } 

因此nanoTime其实算出来的是一个相对的时间,相对于系统启动的时候的时间

Java里currentTimeMillis的实现

我们其实可以写一个简单的例子从侧面来验证currentTimeMillis返回的到底是什么值

 public static void main(String args[]) { System.out.println(new Date().getTime()-new Date(0).getTime()); System.out.println(System.currentTimeMillis()); } 

你将看到输出结果会是两个一样的值,这说明了什么?另外new Date(0).getTime()其实就是1970/01/01 08:00:00,而new Date().getTime()是返回的当前时间,两个日期一减,其实就是当前时间距离1970/01/01 08:00:00有多少毫秒,而System.currentTimeMillis()返回的正好是这个值,也就是说System.currentTimeMillis()就是返回的当前时间距离1970/01/01 08:00:00的毫秒数。

就实现上来说,currentTimeMillis其实是通过gettimeofday来实现的

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); } 

至此应该大家也清楚了,为什么currentTimeMillis返回的值并不是nanoTime返回的值的1000000倍左右了,因为两个值的参照不一样,所以没有可比性

    原文作者:JVM
    原文地址: https://juejin.im/entry/5860c0fe570c35006946af25
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞