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(>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原理详解的更多相关文章
- jQuery 源码分析(十六) 事件系统模块 底层方法 详解
jQuery事件系统并没有将事件监听函数直接绑定到DOM元素上,而是基于数据缓存模块来管理监听函数的,事件模块代码有点多,我把它分为了三个部分:分底层方法.实例方法和便捷方法.ready事件来讲,好理 ...
- jQuery 源码分析(十五) 数据操作模块 val详解
jQuery的属性操作模块总共有4个部分,本篇说一下最后一个部分:val值的操作,也是属性操作里最简单的吧,只有一个API,如下: val(vlaue) ;获取匹配元素集合中第一个元素的 ...
- jQuery 源码分析(十三) 数据操作模块 DOM属性 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第2个部分:DOM属性部分,用于修改DOM元素的属性的(属性和特性是不一样的,一般将property翻译为属性,attribute翻译为特性) DO ...
- Vue.js 源码分析(三十一) 高级应用 keep-alive 组件 详解
当使用is特性切换不同的组件时,每次都会重新生成组件Vue实例并生成对应的VNode进行渲染,这样是比较花费性能的,而且切换重新显示时数据又会初始化,例如: <!DOCTYPE html> ...
- Vue.js 源码分析(三十) 高级应用 函数式组件 详解
函数式组件比较特殊,也非常的灵活,它可以根据传入该组件的内容动态的渲染成任意想要的节点,在一些比较复杂的高级组件里用到,比如Vue-router里的<router-view>组件就是一个函 ...
- Vue.js 源码分析(二十七) 高级应用 异步组件 详解
当我们的项目足够大,使用的组件就会很多,此时如果一次性加载所有的组件是比较花费时间的.一开始就把所有的组件都加载是没必要的一笔开销,此时可以用异步组件来优化一下. 异步组件简单的说就是只有等到在页面里 ...
- Vue.js 源码分析(二十五) 高级应用 插槽 详解
我们定义一个组件的时候,可以在组件的某个节点内预留一个位置,当父组件调用该组件的时候可以指定该位置具体的内容,这就是插槽的用法,子组件模板可以通过slot标签(插槽)规定对应的内容放置在哪里,比如: ...
- Vue.js 源码分析(二十三) 指令篇 v-show指令详解
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...
- Vue.js 源码分析(二十一) 指令篇 v-pre指令详解
该指令会跳过所在元素和它的子元素的编译过程,也就是把这个节点及其子节点当作一个静态节点来处理,例如: <!DOCTYPE html> <html lang="en" ...
随机推荐
- 【Codeforces Round #431 (Div. 1) B】
[链接]h在这里写链接 [题意] 场上有 n 个点,它们分别向上与向右在不同时刻开始运动,相遇则改变移动方向,求最终这些点到达的坐标. [题解] 先把每个点的坐标都往它本该移动的方向相反的方向退ti个 ...
- 基于StringUtils工具类的常用方法介绍(必看篇)
前言:工作中看到项目组里的大牛写代码大量的用到了StringUtils工具类来做字符串的操作,便学习整理了一下,方便查阅. isEmpty(String str) 是否为空,空格字符为false is ...
- Oracle10g中阻塞锁查询更简单
http://blog.itpub.net/195110/viewspace-677572/ http://blog.sina.com.cn/s/blog_636415010100khcl.html
- Oracle分页查询的一个存储过程:
create or replace procedure AspNetOraclePager( tableName in varchar2, --表名 fields in var ...
- Android 中AIDL的使用与理解
AIDL的使用: 最常见的aidl的使用就是Service的跨进程通信了,那么我们就写一个Activity和Service的跨进程通信吧. 首先,我们就在AS里面新建一个aidl文件(ps:现在AS建 ...
- Asp.NETCore让FromServices回来
起因 这两天,我忽然有点怀念 Asp.NET MVC 5 之前的时代,原因是我看到项目里面有这么一段代码(其实不止一段,几乎每个 Controller 都是) [Route("home&qu ...
- Vue实现上传图片功能
前言: 用vue实现上传图片功能,效果图如下: 先说文件上传控件样式美化怎么做,我有两种方法. 1.先上代码 html部分: <div class="pics-wrapper" ...
- 中间件、服务器和Web服务器三者的区别
相信很多的Web安全初学者和我一样,对中间件和服务器的认识不够深刻,对两者的概念可能会有所混淆. 正好今天在学习的时候突然想到了这个问题,粗略百度了一下,似乎网上对这个问题的解释不多,那么就由我来为大 ...
- SDK应该包括什么东西
作者:朱金灿 来源:http://blog.csdn.net/clever101 如果让你设计和开发一个SDK(软件二次开发包),你认为里面应该有什么东西?首先接口文件和库文件之类是必不可少的,否则别 ...
- 看朋友日志发现的一个ios下block相关的内存管理问题,非常奇怪,请大家帮忙一起来回答!
http://blog.csdn.net/fengsh998/article/details/38090205 这篇文章以下是我的回复.相同的代码仅仅是把变量的定义从局部变量改为类的成员变量就发现了非 ...