Let’s start with System.currentTimeMillis() . Usually, the best place to start the exploration is the documentation written in the Javadoc, and there is a lot there to take in. Here is an excerpt of what is important to us right now.

As we can see, the clock provides us with a millisecond precision value but the actual resolution depends on the operating system. Moreover, if we measure the latency by measuring the execution time, it will be way below 1 millisecond, so it’s maybe not a surprise that the answer to the first question was yes.

But can it go backwards? The Javadoc doesn’t mention anything about monotonicity, so we need to dig deeper, and take a look at the implementation.

The method is native, so the implementation depends on the underlying OS. The native implementation for Linux and MacOS look almost identical.

The functions invoke exactly the same syscall, gettimeofday . The man page can provide us with more info, but more importantly with some valuable notes:

As noted above, the time is affected by discontinuous jumps in the system time, which could be backwards, hence the clock is not monotonic. The answer to the third question was yes which does make sense: if we change the current time to one hour ago, we still want currentTimeMillis to return current time, even though the definition of the current time has changed. That’s why it’s often called wall-clock time, the clock on the wall can also jump back in time if we adjust it.

The nanos of the current time

The same exploration path can be taken for System.nanoTime() . Let’s start from the Javadoc which has even more intriguing details than the previous one; here is an excerpt.

Javadoc /** * Returns the current value of the running Java Virtual Machine's * high-resolution time source, in nanoseconds. * * This method can only be used to measure elapsed time and is * not related to any other notion of system or wall-clock time. * The value returned represents nanoseconds since some fixed but * arbitrary <i>origin</i> time (perhaps in the future, so values * may be negative) ... * * <p>This method provides nanosecond precision, but not necessarily * nanosecond resolution ... * * <p>The values returned by this method become meaningful only when * the difference between two such values, obtained within the same * instance of a Java virtual machine, is computed. * * ... */ public static native long nanoTime();

Apparently, the time returned by this clock isn’t related to any real-world time; it can only be used to compare the timestamps within the same JVM instance, and it’s relative to an arbitrary “origin” which can be in the future, and therefore it might be negative – which answers the sixth question. Similar to currentTimeMillis , this method provides nanosecond precision, but not necessarily nanosecond resolution.

Nano time can only be used to measure time intervals, so it ought to be monotonic, right? Unfortunately, the Javadoc doesn’t say anything about monotonicity, so the next step is the implementation.

Linux jlong os::javaTimeNanos() { if (os::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; } }

Here comes the first surprise: nano time is indeed monotonic but only if the underlying operating system supports it. To be fair, any modern Linux server supports CLOCK_MONOTONIC ; there are, however, some rare situations in which it might not hold true.

MacOS 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, &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; }

The first thing that stands out is the giant wall of comments. As software engineers, we know that if there is a long comment then something dodgy must be going on. Indeed, the comment is quite interesting. The call to mach_absolute_time uses the RDTSC instruction underneath which can potentially lead to non-monotonic behaviour on machines with multiple CPU sockets, which recently span up another thought-provoking discussion on the mechanical sympathy mailing list.