JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
上周@望陶問了我一個現(xiàn)象很詭異的問題,說JDK7和JDK8下的System.nanoTime()輸出完全不一樣,而且差距還非常大,是不是兩個版本里的實現(xiàn)不一樣,之前我也沒注意過這個細(xì)節(jié),覺得非常奇怪,于是自己也在本地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下跑了一把,發(fā)現(xiàn)兩個版本下的值基本上差不多的,也就是主要是mac下的實現(xiàn)可能不一樣
于是我又調(diào)用System.currentTimeMillis(),發(fā)現(xiàn)其輸出結(jié)果和System.nanoTime()也完全不是1000000倍的比例
~/Documents/workspace/Test/src ? /Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/bin/java NanosTest 1563115443175 1480265707257另外System.nanoTime()輸出的到底是什么東西,這個數(shù)字好奇怪
這三個小細(xì)節(jié)平時沒有留意,好奇心作祟,于是馬上想一查究竟
再列下主要想理清楚的三個問題
- 在mac下發(fā)現(xiàn)System.nanoTime()在JDK7和JDK8下輸出的值怎么完全不一樣
- System.nanoTime()的值很奇怪,究竟是怎么算出來的
- System.currentTimeMillis()為何不是System.nanoTime()的1000000倍
MAC不同JDK版本下nanoTime實現(xiàn)異同
在mac下,首先看JDK7的nanoTime實現(xiàn)
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下的實現(xiàn)
#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_invalidatereturn (prev == obsv) ? now : obsv; }#else // __APPLE__果然發(fā)現(xiàn)JDK8下多了一個__APPLE__宏下定義的實現(xiàn),和JDK7及之前的版本的實現(xiàn)是不一樣的,不過其他BSD系統(tǒng)是一樣的,只是macos有點不一樣,因為平時咱們主要使用的環(huán)境還是Linux為主,因此對于macos下具體異同就不做過多解釋了,有興趣的自己去研究一下。
Linux下nanoTime的實現(xiàn)
在linux下JDK7和JDK8的實現(xiàn)都是一樣的
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 exitvoid* 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 clockdlclose(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函數(shù),如果定義了,就直接調(diào)用這個函數(shù)來獲取時間,注意下上面的傳給clock_gettime的一個參數(shù)是CLOCK_MONOTONIC,至于這個參數(shù)的作用后面會說,這個函數(shù)在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_GETTIMESYSDEP_GETTIME; #endif#ifndef HANDLED_REALTIMEcase CLOCK_REALTIME:{struct timeval tv;retval = gettimeofday (&tv, NULL);if (retval == 0)TIMEVAL_TO_TIMESPEC (&tv, tp);}break; #endifdefault: #ifdef SYSDEP_GETTIME_CPUSYSDEP_GETTIME_CPU (clock_id, tp); #endif #if HP_TIMING_AVAILif ((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_CPUTIMEcase 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)而對應(yīng)的宏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. */最終是調(diào)用的clock_gettime系統(tǒng)調(diào)用:
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里取納秒數(shù)時傳入的是CLOCK_MONOTONIC這個參數(shù),因此會調(diào)用如下的方法
notrace static noinline int do_monotonic(struct timespec *ts) {unsigned long seq, ns, secs;do {seq = read_seqbegin(>od->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(>od->lock, seq)));vset_normalized_timespec(ts, secs, ns);return 0; }上面的wall_to_monotonic的tv_sec以及tv_nsec都是負(fù)數(shù),在系統(tǒng)啟動初始化的時候設(shè)置,記錄了啟動的時間
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其實算出來的是一個相對的時間,相對于系統(tǒng)啟動的時候的時間
Java里currentTimeMillis的實現(xiàn)
我們其實可以寫一個簡單的例子從側(cè)面來驗證currentTimeMillis返回的到底是什么值
public static void main(String args[]) {System.out.println(new Date().getTime()-new Date(0).getTime());System.out.println(System.currentTimeMillis());}你將看到輸出結(jié)果會是兩個一樣的值,這說明了什么?另外new Date(0).getTime()其實就是1970/01/01 08:00:00,而new Date().getTime()是返回的當(dāng)前時間,兩個日期一減,其實就是當(dāng)前時間距離1970/01/01 08:00:00有多少毫秒,而System.currentTimeMillis()返回的正好是這個值,也就是說System.currentTimeMillis()就是返回的當(dāng)前時間距離1970/01/01 08:00:00的毫秒數(shù)。
就實現(xiàn)上來說,currentTimeMillis其實是通過gettimeofday來實現(xiàn)的
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); }至此應(yīng)該大家也清楚了,為什么currentTimeMillis返回的值并不是nanoTime返回的值的1000000倍左右了,因為兩個值的參照不一樣,所以沒有可比性
總結(jié)
以上是生活随笔為你收集整理的JVM源码分析之System.currentTimeMillis及nanoTime原理详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TODO:Laravel增加验证码
- 下一篇: 魅族 FlymeAuto 车机系统界面曝