为什么System.nanoTime()和System.currentTimeMillis()会如此迅速地分离?

出于诊断目的,我希望能够在长时间运行的服务器应用程序中检测到系统时钟的变化。 由于System.currentTimeMillis()是基于挂钟时间和System.nanoTime()是基于一个独立的(*)挂钟时间系统计时器,我想我可以使用这些值之间的差异的变化来检测系统时间变化。

我写了一个快速testing应用程序,看看这些值之间的差异是多么稳定,令我惊讶的是,价值每秒几毫秒的水平立即对我产生了分歧。 有几次,我看到更快的分歧。 这是在Java 6的Win7 64位桌面上。我还没有在Linux(或Solaris或MacOS)下面试过这个testing程序,看看它是如何执行的。 对于这个应用程序的一些运行,分歧是积极的,有些运行是负面的。 这似乎取决于桌面正在做什么,但很难说。

 public class TimeTest { private static final int ONE_MILLION = 1000000; private static final int HALF_MILLION = 499999; public static void main(String[] args) { long start = System.nanoTime(); long base = System.currentTimeMillis() - (start / ONE_MILLION); while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { // Don't care if we're interrupted } long now = System.nanoTime(); long drift = System.currentTimeMillis() - (now / ONE_MILLION) - base; long interval = (now - start + HALF_MILLION) / ONE_MILLION; System.out.println("Clock drift " + drift + " ms after " + interval + " ms = " + (drift * 1000 / interval) + " ms/s"); } } } 

Thread.sleep()时间的不准确以及中断与计时器漂移完全无关。

这两个Java“系统”调用都是用作度量的 – 一个是测量挂钟时间的差异,另一个是测量绝对时间间隔,所以当实时时钟没有改变时,这些值应该改变非常接近相同的速度,对不对? 这是Java的错误还是弱点? 有没有在操作系统或硬件,防止Java更准确?

我完全期待这些独立测量之间存在一些漂移和抖动(**),但是我预计每分钟漂移不到一分钟。 每秒1毫秒的漂移,如果单调,几乎是90秒! 我最坏的情况下观察到的漂移可能是十倍。 每次运行这个程序时,我都会看到第一次测量时出现漂移。 到目前为止,我还没有运行该程序超过30分钟左右。

由于抖动,我期望在打印的数值上看到一些小的随机性,但是在程序的几乎所有运行中,我都看到差异的稳定增加,通常每秒增加3毫秒,而且几乎比。

Windows的任何版本是否都有类似于Linux的机制来调整系统时钟速度,以使时钟与外部时钟源慢慢同步? 这样的事情会影响两个计时器,还是只有挂钟计时器?

(*)我明白,在某些体系结构中, System.nanoTime()必然会使用与System.currentTimeMillis()相同的机制。 我也相信,假设现代Windows服务器不是这样的硬件架构是公平的。 这是一个坏的假设?

(**)当然, System.currentTimeMillis()通常比System.nanoTime()具有更大的抖动,因为在大多数系统上它的粒度不是1毫秒。

你可能会发现这个关于JVM定时器的Sun / Oracle博客文章很有意思。

这篇文章中有几篇关于Windows下的JVM定时器的段落:

System.currentTimeMillis()是使用GetSystemTimeAsFileTime方法实现的,它基本上只读取Windows维护的低分辨率时间值。 根据所报告的信息,读取这个全局变量自然是非常快的 – 大约6个周期。 无论定时器中断是如何编程的,这个时间值都会以恒定的速率更新 – 取决于平台,这将是10ms或15ms(该值似乎与默认的中断周期相关)。

System.nanoTime()是使用QueryPerformanceCounter / QueryPerformanceFrequency API(如果可用,否则返回currentTimeMillis*10^6 )实现的。 QueryPerformanceCounter (QPC)根据运行的硬件以不同的方式实现。 通常,它将使用可编程间隔定时器(PIT)或ACPI电源管理定时器(PMT),或CPU级别的时间戳计数器(TSC)。 访问PIT / PMT需要执行慢I / O端口指令,因此QPC的执行时间大约为几微秒。 相反,读取TSC的时间大约为100个时钟周期(从芯片读取TSC并将其转换为基于工作频率的时间值)。 您可以通过检查QueryPerformanceFrequency是否返回3,579,545(即3.57MHz)的签名值来判断您的系统是否使用ACPI PMT。 如果你看到一个大约1.19Mhz的值,那么你的系统正在使用旧的8245 PIT芯片。 否则,您应该看到一个大约与CPU频率相关的值(以任何可能有效的速度调节或电源管理为模)。

我不确定这实际上会有多大帮助。 但是这是Windows / Intel / AMD / Java世界中积极变化的一个领域。 对于几个(至少10年)的时间来说,准确和精确的时间测量的需求是显而易见的。 英特尔和AMD都通过改变TSC的工作方式做出了回应。 两家公司现在都有一些名为Invariant-TSC和/或Constant-TSC的东西

检查CPU内核的rdtsc准确性 。 从osgx(引用英特尔手册)引用。

“16.11.1不变的TSC

较新的处理器中的时间戳计数器可以支持被称为不变TSC的增强。 处理器对不变TSC的支持由PUID.80000007H:EDX [8]表示。

不变的TSC将在所有ACPI P-,C-中以恒定速率运行。 和T状态。 这是建筑行为向前发展。 在支持不变TSC的处理器上,操作系统可以使用TSC作为挂钟定时器服务(而不是ACPI或HPET定时器)。 TSC读取效率更高,不会产生与环路转换或访问平台资源相关的开销。“

另见http://www.citihub.com/requesting-timestamp-in-applications/ 从作者引用

对于AMD:

如果CPUID 8000_0007.edx [8] = 1,则确保TSC速率在所有P-状态,C-状态和停止授权转换(例如STPCLK节流)中不变。 因此,TSC适合用作时间的来源。

对于Intel:

处理器对不变TSC的支持由CPUID.80000007H:EDX [8]表示。 不变的TSC将在所有ACPI P-,C-中以恒定速率运行。 和T状态。 这是建筑行为向前发展。 在支持不变TSC的处理器上,操作系统可以使用TSC作为挂钟定时器服务(而不是ACPI或HPET定时器)。 TSC读取效率更高,不会产生与环路转换或访问平台资源相关的开销。“

现在真正重要的一点是最新的JVM似乎利用了最新的可靠TSC机制。 没有太多的在线显示这一点。 不过,请查看http://code.google.com/p/disruptor/wiki/PerformanceResults

“为了测量延迟,我们采用三级流水线,产生低于饱和的事件,这是通过在注入事件之前等待1微秒来实现的,然后再注入下一个事件并重复5千万次,为了达到这个精确度,有必要使用来自CPU的时间戳计数器我们选择具有不变TSC的CPU,因为旧的处理器由于省电和睡眠状态而频繁变化,Intel Nehalem和后续处理器使用一个不变的TSC,最新的Oracle JVM可以访问Ubuntu 11.04。这个测试没有使用CPU绑定“

请注意,“Disruptor”的作者与在Azul和其他JVM上工作的人关系密切。

另请参阅“Java幕后花絮”。 本演示文稿提到了新的不变TSC指令。

“以毫微秒为单位返回最精确的可用系统定时器的当前值。

“这种方法只能用来测量经过的时间,并不涉及系统或挂钟时间的任何其他概念,返回的值表示一些固定但随意​​的时间(可能在将来,因此值可能是负的) 。这种方法提供了纳秒级的精度,但不一定是纳秒级的精度,不能保证值的改变频率。连续调用的时间跨度大于292年(2 ** 63纳秒)的差异将不能准确地计算经过时间溢出。”

请注意,它表示“精确”,而不是“准确”。

这不是“Java中的错误”或任何东西的“错误”。 这是一个定义。 JVM开发人员四处寻找系统中最快的时钟/定时器并使用它。 如果这与系统时钟锁定在一起,那么就好,但如果不是的话,这就是cookie崩溃的方式。 例如,计算机系统将具有精确的系统时钟,但内部具有与CPU时钟速率相关的更高速率定时器等是完全合理的。 由于时钟频率经常变化以最小化功耗,所以这个内部定时器的增加率会变化。

System.currentTimeMillis()System.nanoTime()不一定由相同的硬件提供。 GetSystemTimeAsFileTime()支持的System.currentTimeMillis()具有100ns的分辨率元素。 它的来源是系统计时器。 System.nanoTime()由系统的高性能计数器支持。 有各种不同的硬件提供这个柜台。 因此,其分辨率因底层硬件而异。

在任何情况下都不能假设这两个来源是同步的。 测量两个值彼此会揭示不同的运行速度。 如果System.currentTimeMillis()的更新被视为真正的进展,则System.nanoTime()的输出有时会比较慢,有时会更快,而且也是变化的。

为了锁定这两个时间源,必须进行仔细的校准。

这两个时间源之间的关系的更详细的描述可以在Windows时间戳项目中找到 。

Windows的任何版本是否都有类似于Linux的机制来调整系统时钟速度,以使时钟与外部时钟源慢慢同步? 这样的事情会影响两个定时器,还是只有挂钟定时器?

Windows时间戳项目做你所要求的。 据我所知,它只影响挂钟定时器。