If you look at the ImmortalCacheEntry implementation, isExpired() just returns false, so it shouldn't appear in the profiler output at all.
You should make sure you're using sampling when profiling, otherwise small methods that are called often will appear to have disproportionately high CPU time.
If you are using sampling, could you share your tests?
D'oh. Of course you're right Dan: Thought I was using sampling but I was not.
Using sampling the eviction appears to be taking somewhat less time:
If I drill down inside 'purgeExpired' it shows BoundedConcurrentHashMap$HashIterator.<init> but the sampling indicates only 25ms total spent here out of 32s total in purgeExpired; I assume this is a limitation of the sampling method.
I believe that if I run eviction somewhat less frequently I will be able to find an acceptable tradeoff.
If you're not instrumenting each method call it can be inlined and it won't show in the profiler, so it makes sense that you don't see anything under purgeExpired.
That still seems like a lot of time in purgeExpired... How big is your cache? Do you use the default wakeupInterval or have you set it lower? And how long does your test take?
Our wakeupInterval was set to 10 seconds at the time I ran this test. Test ran for 20 minutes so perhaps 120 total invocations across each of 8 cache regions. Eviction was approx 5% of the total CPU time during this test. Less than 100k total cache entries.
I expect to be profiling again next week. I have decreased wakeupInterval to 30 seconds so the impact should be less.