Home > Articles > Programming > Java

Java Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents


Last updated Mar 14, 2003.

Previously in the Java Reference Guide I have written about caches and pools, including a discussion of the Apache Java Caching System, so I thought it would be a good idea to review EHCache, which claims to be the most widely used Java cache. While my personal observations do not serve as an empirical validation for their claim, I have seen more instances of EHCache in the industry that any other open source caching solution. In particular, EHCache seems to commonly be found along side Hibernate as its second-level cache, which means that Hibernate can be configured to store the results of its queries in a cache in order to offload a large percentage of calls back to the database (and while the majority of ORM installations in the industry are implementations of Hibernate, the majority of Hibernate second-level caches are EHCache.)

Before we get into the details of incorporating EHCache into your application, it might be a good refresher to review what a cache is and the benefits that it can provide to your application.

A cache is akin to a data structure that can store stateful instances of your application objects, but with the extension that advanced caches such as EHCache provide far more than a simple data structure but also means to manage concurrency, maintain the coherence of the cache, and distribution strategies for running multiple instances of your cache across multiple machines. The purpose of a cache is to store instances of business objects that you will reference again so that you do not need to retrieve them again from somewhere like a database or web service call or reconstruct them if they are difficult or time consuming to build. If an object is contained in your cache then you can use that instance, which should be located in your working memory and bypass a network call to get to the object.

Several years ago Marc Fleury, the founder of JBoss, wrote a paper endorsing entity beans in the architecture of an enterprise Java application. One of the things in his list of arguments that struck me as a foundational truth is that it is faster to retrieve an object from memory rather than query a database for the object. While many may argue whether entity beans were ever good things, few could argue that reading from memory is faster than a network call to a database. In short, what Marc was arguing with this point is that the entity bean cache was a good thing.

But using a cache is not always sunshine and roses nor is it the solution to all of your business problems. There are many anti-patterns about when you should not use caches, but the decision can really be boiled down to how you expect to use your objects. Are you going to be frequently reading the same objects over and over again? If so then they are good candidates to be cached. If, on the other hand, you will seldom read the same object, but instead will query for a different instance, then a cache will simply take up memory and add additional overhead to your application processing. Consider the steps you have to follow to unsuccessfully find an object in a cache:

  1. Your application needs an object so it queries the cache for the object and cannot find it
  2. Your application then loads the object from the database
  3. In order to keep the cache up-to-date, if the cache is full then you need to find an object to evict from the cache (such as by using a least recently used algorithm) and then insert the new object
  4. Return the object to the calling process

When you look in the cache for an object and find it, it is referred to as a "hit", but if you do not find the instance you're looking for then it is called a "miss". If you find that the majority of requests are misses then either your cache is too small or you are using a cache when you really shouldn't.

This brings us to another point: because caches hold stateful objects, such as user instances or products that your company is selling, then they must be of a finite size or you risk consuming more memory than your JVM can support. There are solutions to avoid this in fourth generation applications that store a vast amount of data distributed across dozens of machines and software infrastructure to locate the object you're looking for, but from a simple application cache perspective, hopefully it makes sense that caching objects that you're seldom going to use is a waste of memory and processing cycles.

With this background I hope that you can appreciate the value in incorporating a cache into your application technology stack. To reiterate, EHCache provides a robust caching solution for hosting your application objects. It can be used with Hibernate as a second-level cache or it can be used directly by your application to store whatever objects you want immediate access to.

But better than just being an in-memory application cache, EHCache can also run in a distributed environment. While it may seem obvious, it is worth reviewing what "distributed" means in the context of a cache. Consider the aforementioned scenario of using a second-level cache behind Hibernate to offload requests to the database. In this scenario Hibernate will first query its cache and if it finds an object then it returns that cached instance rather than re-query the database. Let's take this a step further and put two application servers into the mix: each server hosts the same application and has its own second-level cache behind Hibernate. Now consider that an object is updated on one server and its state persisted back to the database. When a request comes for the updated object on the other application server instance, what does it return? If the object exists in the second-level cache then, by definition, it will not re-query the database, but its data is stale because the underlying data has changed.

The solution to this problem is that when an object is changed in one cache instance, the other instances must be notified of the change. The problem we just uncovered is referred to as cache coherency, or the consistency of data stored in local caches. As with all configurations that affect performance, there is a trade off in how concurrent you want your cache to how performant you want your cache. 100% concurrency requires overhead to ensure that all cache nodes are in sync with one another before committing a write whereas "eventually correct" allows you to write to one cache instance quickly and know that eventually all of the nodes will be correct. There is still the possibility that someone reading another cache instance will read stale data, but if that is acceptable to your business requirements you can gain performance improvements by being lax on your concurrency requirements.

EHCache provides several concurrency options, including the use of a centralized server or a Terracotta server array as well as peer-to-peer distribution strategies that include RMI, JMS, and JGroups. In this article series I will review the peer-to-peer options and take a deep-dive into RMI-based replication. You can read more about configuring EHCache with Terracotta in the Terracotta section of the Java Reference Guide.