概述

上周@望陶问了我一个现象很诡异的问题,说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_invalidatereturn (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 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函数,如果定义了,就直接调用这个函数来获取时间,注意下上面的传给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_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)

而对应的宏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源码分析之System.currentTimeMillis及nanoTime原理详解相关推荐

  1. nanotime java 博客园_JVM源码分析之System.currentTimeMillis及nanoTime原理详解

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

  2. 【SA8295P 源码分析】53 - mifs.build.tmpl 脚本详解:启动QNX procnto-smp-instr微内核、启动QNX串口终端shell、加载解析并执行ifs2_la.img

    [SA8295P 源码分析]53 - mifs.build.tmpl 脚本详解:启动QNX procnto-smp-instr微内核.启动QNX串口终端shell.加载解析并执行ifs2_la.img ...

  3. Vue.js 源码分析(二十三) 指令篇 v-show指令详解

    v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...

  4. 从源码分析RocketMQ系列-消息拉取PullMessageProcessor详解

    导语   在之前的分析中分析了关于SendMessageProcessor,并且提供了对应的源码分析分析对于消息持久化的问题,下面来看另外一个PullMessageProcessor,在RocketM ...

  5. Vue.js 源码分析(五) 基础篇 方法 methods属性详解

    methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...

  6. Android进阶——ExoPlayer源码分析之宽带预测策略的算法详解

    前言 由于国内基础设施非常优秀,在平时的开发中,很少会关注网络情况,很容易忽略弱网情况下的网络状况,如果项目属于国外App,则需要考虑到当前的基础设施和网络情况,特别是播放视频的时候,需要通过动态调整 ...

  7. (01)ORB-SLAM2源码无死角解析-(37) EPnP 算法原理详解→理论基础一:控制点选取、透视投影约束

    讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下: (01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲 ...

  8. android doze模式源码分析,Android Doze模式启用和恢复详解

    从Android 6.0(API level 23)开始,Android提出了两个延长电池使用时间的省电特性给用户.用户管理可以在没有充电的情况下管理app的行为.当用户一段时间没有使用手机的时候,D ...

  9. notification源码分析_状态栏通知Notification、NotificationManager详解(源码)----转载...

    在Android系统中,发一个状态栏通知还是很方便的.下面我们就来看一下,怎么发送状态栏通知,状态栏通知又有哪些参数可以设置? 首先,发送一个状态栏通知必须用到两个类: NotificationMan ...

最新文章

  1. asp创建mysql表_创建一个数据库,用ASP怎么写?
  2. 【Python】解决Django Admin管理界面样式表(CSS Style)丢失问题
  3. [bzoj2893] 集合计数
  4. mysql的字符集设置为什么_为什么Mysql默认的字符集都是latin1
  5. 在ECS实例的centos系统中安装Hadoop
  6. 视频分享:挨踢项目求生法则(1)——团队建设篇
  7. LeetCode 532. 数组中的K-diff数对
  8. simplifyEnrichment,一个对GO富集结果进行聚类和可视化的工具
  9. 阿里面试题剖析,如何保证消息不被重复消费?
  10. python列表引用_Python列表(list)的方法调用
  11. 17muduo_base库源码分析(八)
  12. data()中的数据可以直接操作
  13. python缓存memoryerror_Python安装会抛出大量MemoryError()的
  14. [postgresql]postgresql的锁介绍
  15. 华为ADSL路由设置
  16. paip.c++ qt creator svn 设置以及使用总结.
  17. Elasticsearch自定义插件
  18. php html5聊天室源码,Grupo Pro v2 - PHP聊天室源码
  19. java applet介绍,Java中的Applet介绍
  20. 四款功能强大的优质app合集,总有一个能给你带来帮助!

热门文章

  1. server输出几行 sql_如何将SQL Server存储过程的输出存储在.txt文件中
  2. python设计一个函数定义计算并返回n价调和函数_音乐编程语言musicpy教程(第三期) musicpy的基础语法(二)...
  3. 利用反射操作bean的属性和方法
  4. Python-条件控制及循环
  5. centos 安装 acrobat Reader之后
  6. php用session制作网站仿恶意刷新计数器
  7. NS_ASSUME_NONNULL_BEGIN,NS_ASSUME_NONNULL_END
  8. tomcat Connector 连接器
  9. ActiveReports 报表应用教程 (4)---分栏报表
  10. jQuery Mobile 手动显示ajax加载器,提示加载中...