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_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(>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
都是负数,在系统启动初始化的时候设置,记录了启动的时间
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原理详解相关推荐
- nanotime java 博客园_JVM源码分析之System.currentTimeMillis及nanoTime原理详解
JDK7和JDK8下的System.nanoTime()输出完全不一样,而且差距还非常大,是不是两个版本里的实现不一样,之前我也没注意过这个细节,觉得非常奇怪,于是自己也在本地mac机器上马上测试了一 ...
- 【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 ...
- Vue.js 源码分析(二十三) 指令篇 v-show指令详解
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...
- 从源码分析RocketMQ系列-消息拉取PullMessageProcessor详解
导语 在之前的分析中分析了关于SendMessageProcessor,并且提供了对应的源码分析分析对于消息持久化的问题,下面来看另外一个PullMessageProcessor,在RocketM ...
- Vue.js 源码分析(五) 基础篇 方法 methods属性详解
methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...
- Android进阶——ExoPlayer源码分析之宽带预测策略的算法详解
前言 由于国内基础设施非常优秀,在平时的开发中,很少会关注网络情况,很容易忽略弱网情况下的网络状况,如果项目属于国外App,则需要考虑到当前的基础设施和网络情况,特别是播放视频的时候,需要通过动态调整 ...
- (01)ORB-SLAM2源码无死角解析-(37) EPnP 算法原理详解→理论基础一:控制点选取、透视投影约束
讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下: (01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲 ...
- android doze模式源码分析,Android Doze模式启用和恢复详解
从Android 6.0(API level 23)开始,Android提出了两个延长电池使用时间的省电特性给用户.用户管理可以在没有充电的情况下管理app的行为.当用户一段时间没有使用手机的时候,D ...
- notification源码分析_状态栏通知Notification、NotificationManager详解(源码)----转载...
在Android系统中,发一个状态栏通知还是很方便的.下面我们就来看一下,怎么发送状态栏通知,状态栏通知又有哪些参数可以设置? 首先,发送一个状态栏通知必须用到两个类: NotificationMan ...
最新文章
- asp创建mysql表_创建一个数据库,用ASP怎么写?
- 【Python】解决Django Admin管理界面样式表(CSS Style)丢失问题
- [bzoj2893] 集合计数
- mysql的字符集设置为什么_为什么Mysql默认的字符集都是latin1
- 在ECS实例的centos系统中安装Hadoop
- 视频分享:挨踢项目求生法则(1)——团队建设篇
- LeetCode 532. 数组中的K-diff数对
- simplifyEnrichment,一个对GO富集结果进行聚类和可视化的工具
- 阿里面试题剖析,如何保证消息不被重复消费?
- python列表引用_Python列表(list)的方法调用
- 17muduo_base库源码分析(八)
- data()中的数据可以直接操作
- python缓存memoryerror_Python安装会抛出大量MemoryError()的
- [postgresql]postgresql的锁介绍
- 华为ADSL路由设置
- paip.c++ qt creator svn 设置以及使用总结.
- Elasticsearch自定义插件
- php html5聊天室源码,Grupo Pro v2 - PHP聊天室源码
- java applet介绍,Java中的Applet介绍
- 四款功能强大的优质app合集,总有一个能给你带来帮助!
热门文章
- server输出几行 sql_如何将SQL Server存储过程的输出存储在.txt文件中
- python设计一个函数定义计算并返回n价调和函数_音乐编程语言musicpy教程(第三期) musicpy的基础语法(二)...
- 利用反射操作bean的属性和方法
- Python-条件控制及循环
- centos 安装 acrobat Reader之后
- php用session制作网站仿恶意刷新计数器
- NS_ASSUME_NONNULL_BEGIN,NS_ASSUME_NONNULL_END
- tomcat Connector 连接器
- ActiveReports 报表应用教程 (4)---分栏报表
- jQuery Mobile 手动显示ajax加载器,提示加载中...