Is System.currentTimeMillis Really a Performance Bottleneck? A Deep Dive with Benchmarks
After observing a middleware that caches System.currentTimeMillis, the author investigates whether this method truly suffers performance issues, debunks common myths with detailed analysis, benchmarks using custom and JMH tests, and concludes that System.currentTimeMillis performs adequately without needing custom caching.
# Question: Does System.currentTimeMillis really have performance problems?
While reading the source code of a middleware, the author found a custom cache class that replaces direct calls to System.currentTimeMillis(). This raised the suspicion that the native method might be too slow.
/**
* Weak‑precision timer, avoids synchronization for performance.
*/
public class TimeUtil {
// cached current milliseconds
private static volatile long CURRENT_TIME = System.currentTimeMillis();
public static final long currentTimeMillis() { return CURRENT_TIME; }
public static final long currentTimeNanos() { return System.nanoTime(); }
// update cache
public static final void update() { CURRENT_TIME = System.currentTimeMillis(); }
}
// a scheduled task updates the cache every second
heartbeatScheduler.scheduleAtFixedRate(processorCheck(), 0L, 1000, TimeUnit.MILLISECONDS);The author searched the web and found many articles claiming that System.currentTimeMillis() is extremely slow—up to 100× slower than creating a new object, or 250× slower under concurrency.
# Analysis of the alleged problems
It does read the system clock (xtime) which is a contended resource, but Linux protects it with a sequence lock, not a mutex, so reads do not block each other.
Calling the kernel is required, but allocating a new object also triggers a system call, so the comparison is misleading.
Published benchmark code mistakenly includes the time spent on CountDownLatch.countDown(), which is implemented with CAS and can dominate the measured cost under heavy concurrency.
Furthermore, the original benchmark measured total execution time of a batch of calls, which is not a fair way to compare single‑call latency.
# Revised benchmarking
By measuring each call individually (using System.nanoTime() before and after the call) and by using the Java Microbenchmark Harness (JMH), the author obtained the following results (illustrated in the images below).
The data shows that in short‑term concurrent scenarios System.currentTimeMillis() can be faster than the cached version, and in single‑threaded tests it is roughly twice as fast as the cache.
# JMH benchmark
Using JMH with double the CPU core count (8 threads) and measuring average time, the results confirm the same conclusion.
# Conclusion
The investigation demonstrates that System.currentTimeMillis() does not suffer from a serious performance issue; its concurrent performance is acceptable and it is even faster than a naïve caching implementation. Therefore, there is no need to replace it with custom cache clocks in typical applications.
# Test code
public class CurrentTimeMillisTest {
public static void main(String[] args) {
int num = 10000000;
System.out.print("Single‑thread " + num + " calls to System.currentTimeMillis total time: ");
System.out.println(singleThreadTest(() -> { long l = System.currentTimeMillis(); }, num));
System.out.print("Single‑thread " + num + " calls to CacheClock.currentTimeMillis total time: ");
System.out.println(singleThreadTest(() -> { long l = CacheClock.currentTimeMillis(); }, num));
System.out.print("Concurrent " + num + " calls to System.currentTimeMillis total time: ");
System.out.println(concurrentTest(() -> { long l = System.currentTimeMillis(); }, num));
System.out.print("Concurrent " + num + " calls to CacheClock.currentTimeMillis total time: ");
System.out.println(concurrentTest(() -> { long l = CacheClock.currentTimeMillis(); }, num));
}
private static long singleThreadTest(Runnable runnable, int num) {
long sum = 0;
for (int i = 0; i < num; i++) {
long begin = System.nanoTime();
runnable.run();
long end = System.nanoTime();
sum += end - begin;
}
return sum;
}
private static long concurrentTest(Runnable runnable, int num) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(200, 200, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(num));
long[] sum = new long[]{0};
CountDownLatch countDownLatch = new CountDownLatch(num);
for (int i = 0; i < num; i++) {
threadPoolExecutor.submit(() -> {
long begin = System.nanoTime();
runnable.run();
long end = System.nanoTime();
synchronized (CurrentTimeMillisTest.class) {
sum[0] += end - begin;
}
countDownLatch.countDown();
});
}
try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
return sum[0];
}
public static class CacheClock {
private static ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);
private static volatile long timeMilis;
static {
timer.scheduleAtFixedRate(() -> { timeMilis = System.currentTimeMillis(); }, 0, 1000, TimeUnit.MILLISECONDS);
}
public static long currentTimeMillis() { return timeMilis; }
}
} @BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 120, time = 1, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(time = 1, timeUnit = TimeUnit.MICROSECONDS)
@Threads(8)
@Fork(1)
@State(Scope.Benchmark)
public class JMHTest {
public static void main(String[] args) throws RunnerException { testNTime(10000); }
private static void testNTime(int num) throws RunnerException {
Options options = new OptionsBuilder()
.include(JMHTest.class.getSimpleName())
.measurementIterations(num)
.output("E://testRecord.log")
.build();
new Runner(options).run();
}
@Benchmark
public long testSystem() { return System.currentTimeMillis(); }
@Benchmark
public long testCacheClock() { return JMHTest.CacheClock.currentTimeMillis(); }
public static class CacheClock {
private static ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);
private static volatile long timeMilis;
static { timer.scheduleAtFixedRate(() -> { timeMilis = System.currentTimeMillis(); }, 0, 1000, TimeUnit.MILLISECONDS); }
public static long currentTimeMillis() { return timeMilis; }
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
