Let's look at the source code :
What happens when we call CacheBuilder.recordStats() ?
CacheBuilder defines a no-op StatsCounter implementation of NULL_STATS_COUNTER , and this is what is used by default. If you call .recordStats() , it is replaced with a SimpleStatsCounter in which there are six LongAddable (usually a LongAdder , but returns to AtomicLong if it cannot use LongAdder ) for each of the statistics it tracks.
Then what happens when we build Cache ?
For the standard LocalCache (this is what you get from CacheBuilder.build() or CacheBuilder.build(CacheLoader) ), it builds the instance of the desired StatsCounter during construction . Each Segment Cache likewise gets its own instance of the same type of StatsCounter . Other Cache implementations may choose SimpleStatsCounter if they so wish, or provide their own behavior (for example, a non-operational implementation).
And when do we use Cache ?
Each call in LocalCache that LocalCache one of the statistical calls calls the corresponding methods StatsCounter.record*() , which in turn causes an atomic increment or addition based on LongAddable . LongAdder documented much faster than AtomicLong , so, as you say, this should be barely noticeable. Although in the case of non-op StatsRecorder JIT can fully optimize record*() calls, which can be noticeable over time. But a decision not to track statistics on this basis would be premature optimization .
And finally, when do we get statistics?
When you call Cache.stats() StatsCounter for Cache and all its Segments are merged together into a new StatsCounter and the result is returned to you. This means that there will be minimal blocking; each field needs to be read only once, and there is no external synchronization or blocking. This means technically the condition of the race (the segment can be obtained in the middle through aggregation), but in practice it does not matter.
So, in the resume?
You should feel comfortable using CacheBuilder.recordStats() on any Cache you need to monitor, and calling Cache.stats() as often as it is useful. The memory overhead is approximately constant, the speed of the overhead is negligible (and faster than any similar monitoring that you are likely to implement), as well as the overhead of the Cache.stats() conflict.
Obviously, a dedicated thread that does nothing but call Cache.stats() in a loop will cause some controversy, but that would be stupid. Any type of periodic access will go unnoticed.
source share