JVM源码分析之System.currentTimeMillis及nanoTime原理详解 概述 MAC不同JDK版本下nanoTime实现异同 Linux下nanoTime的实现 Java里currentTimeMillis的实现

本文来自PerfMa技术社区:https://club.perfma.com
PerfMa(笨马网络)官网:https://www.perfma.com

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

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

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

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

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

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

再来看JDK8下的实现

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

Linux下nanoTime的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

_clock_gettime的定义在

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

而对应的宏SYSDEP_GETTIME定义如下:

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

Java里currentTimeMillis的实现

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

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里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来实现的

JVM源码分析之System.currentTimeMillis及nanoTime原理详解
概述
MAC不同JDK版本下nanoTime实现异同
Linux下nanoTime的实现
Java里currentTimeMillis的实现

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

推荐阅读

世界最优秀的分布式文件系统架构演进之路

一次百万长连接压测 Nginx OOM 的问题排查分析