Guva is an open-source public java library of google, similar to Apache Commons. It provides a collection, reflection, caching, scientific computing, xml, io and other tool class libraries.
Cache is just one of the modules. Using Guva cache can build local cache conveniently and quickly.
[TOC]
Building the first cache using Guava
First, we need to add guava dependency to maven project.
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>25.0-jre</version> </dependency>
Create a cache using Guava
// Building a cache instance through CacheBuilder Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // Set the maximum capacity of the cache .expireAfterWrite(1, TimeUnit.MINUTES) // Set cache invalidates one minute after write .concurrencyLevel(10) // Set concurrency level to 10 .recordStats() // Enable cache statistics .build(); // Put in cache cache.put("key", "value"); // Get cache String value = cache.getIfPresent("key");
expireAfterWrite cache will fail directly in a certain period of time
After the expireAfterAccess cache is accessed, it will expire after a certain period of time
getIfPresent returns null if it does not exist
The code demonstrates the use of Guava to create a memory based local cache, and specifies some cache parameters, such as cache capacity, cache expiration time, concurrency level, etc., then put a cache through the put method and use getIfPresent to get it.
Cache and LoadingCache
Cache is built by the build() method of CacheBuilder. It is the most basic cache interface provided by Gauva, and it provides some commonly used cache APIs:
Cache<Object, Object> cache = CacheBuilder.newBuilder().build(); // Put / overwrite a cache cache.put("k1", "v1"); // Gets a cache and returns a null value if the cache does not exist Object value = cache.getIfPresent("k1"); // Get the cache. When the cache does not exist, it will be loaded and returned through Callable. The operation is atomic Object getValue = cache.get("k1", new Callable<Object>() { @Override public Object call() throws Exception { return null; } });
LoadingCache
LoadingCache inherits from Cache. When building LoadingCache, it needs to be built through the build method of CacheBuilder (cacheloader <? Super K1, V1 > loader)
CacheBuilder.newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // Cache load logic ... } });
It can load the cache spontaneously through CacheLoader
LoadingCache<Object, Object> loadingCache = CacheBuilder.newBuilder().build(new CacheLoader<Object, Object>() { @Override public Object load(Object key) throws Exception { return null; } }); // Get the cache. When the cache does not exist, it will be loaded automatically through CacheLoader. This method will throw an ExecutionException exception. loadingCache.get("k1"); // Obtain the cache in an unsafe way. When the cache does not exist, it will be loaded automatically through CacheLoader. This method will not throw an exception loadingCache.getUnchecked("k1");
Cache concurrency level
Guava provides an api to set the level of concurrency so that the cache supports concurrent writes and reads. Similar to ConcurrentHashMap, the concurrency of Guava cache is realized by separating locks. In general, it is a good choice to set the concurrency level as the number of cpu cores in the server.
CacheBuilder.newBuilder() // Set the concurrency level to the number of cpu cores .concurrencyLevel(Runtime.getRuntime().availableProcessors()) .build();
Initial capacity of cache
We can set a reasonable initial capacity for the cache when building the cache. Because Guava's cache uses the mechanism of separating locks, the cost of capacity expansion is very expensive. Therefore, a reasonable initial capacity can reduce the number of cache container expansion.
CacheBuilder.newBuilder() // Set the initial capacity to 100 .initialCapacity(100) .build();
When using the maximum capacity based recycling policy, we need to set two necessary parameters:
- maximumWeigh; specifies the maximum capacity.
- Weigher; calculates the size of the cache capacity when the cache is loaded.
Here we give an example of a cache in which both key and value are of String type:
CacheBuilder.newBuilder() .maximumWeight(1024 * 1024 * 1024) // Set the maximum capacity to 1M // Set Weigher to calculate the cache capacity .weigher(new Weigher<String, String>() { @Override public int weigh(String key, String value) { return key.getBytes().length + value.getBytes().length; } }).build();
When the maximum number / capacity of cache approaches or exceeds the maximum value set by us, Guava will use LRU algorithm to recover the previous cache.
Recovery based on soft / weak reference
Reference based recycling strategy is unique in java. In java, there is an automatic object recycling mechanism. According to the different ways programmers create objects, objects are divided into strong reference, soft reference, weak reference and virtual reference from strong to weak. For these references, they have the following differences
Strong citation
Object o=new Object(); // Strong citation
When the memory space is insufficient, the garbage collector will not automatically recycle a referenced strong reference object, but will directly throw an OutOfMemoryError error, causing the program to terminate abnormally.
Soft reference
Compared with strong reference, soft reference is an unstable reference method. If an object has soft reference, GC will not actively recycle soft reference object when memory is sufficient, and soft reference object will be recycled when memory is insufficient.
SoftReference<Object> softRef=new SoftReference<Object>(new Object()); // Soft reference Object object = softRef.get(); // Get soft reference
Using soft reference can prevent memory leakage and enhance the robustness of the program. But do a good job in null detection.
Weak reference
Weak reference is a more unstable way of reference than soft reference, because weak reference objects are likely to be recycled regardless of memory.
WeakReference<Object> weakRef = new WeakReference<Object>(new Object()); // Weak reference Object obj = weakRef.get(); // Get weak reference
Virtual reference
Virtual reference is the same as virtual reference, because if an object only holds virtual reference, it is the same as no reference. It is also rarely used in practice.
In Guava cache, soft / weak reference cache recovery is supported. Using this method can greatly improve the memory utilization, and there will be no memory overflow exception.
CacheBuilder.newBuilder() .weakKeys() // Use a weak reference store key. The cache may be reclaimed when the key has no other (strong or soft) references. .weakValues() // Store values with weak references. When a value has no other (strong or soft) references, the cache may be reclaimed. .softValues() // Store values using soft references. When memory is low and the value is referenced by other strong references, the cache is reclaimed .build();
By the way of soft / weak reference recovery, it is equivalent to giving the cache recovery task to GC, which makes the cache hit rate very unstable. In the case of unnecessary, it is recommended to recycle based on quantity and capacity.
Explicit recovery
After the Cache is built, we can explicitly recycle the Cache through the interface provided by Cache, for example:
// Build a cache Cache<String, String> cache = CacheBuilder.newBuilder().build(); // Reclaim the cache with key k1 cache.invalidate("k1"); // Batch reclaim the cache with key of k1 and k2 List<String> needInvalidateKeys = new ArrayList<>(); needInvalidateKeys.add("k1"); needInvalidateKeys.add("k2"); cache.invalidateAll(needInvalidateKeys); // Reclaim all caches cache.invalidateAll();
Expiration policy and refresh of cache
Guava also provides cache expiration and refresh policies.
Cache expiration policy
The expiration policy of cache is divided into fixed time and relative time.
Fixed time generally refers to how long the cache expires after writing. For example, we build a cache that expires 10 minutes after writing:
CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) // Expires after 10 minutes of writing .build(); // You can use the Duration setting after java8 CacheBuilder.newBuilder() .expireAfterWrite(Duration.ofMinutes(10)) .build();
The relative time is generally relative to the access time, that is, the expiration time of the cache will be refreshed after each access, which is similar to the session expiration time in the servlet. For example, to build a cache that will expire if it is not accessed within 10 minutes:
CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES) //Expires if not accessed within 10 minutes .build(); // You can use the Duration setting after java8 CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofMinutes(10)) .build();
Cache refresh
In Guava cache, timed refresh and explicit refresh are supported, among which only LoadingCache can perform timed refresh.
Timed refresh
When we refresh the cache regularly, we need to specify the refresh interval of the cache and a CacheLoader to load the cache. When the refresh interval is reached, the next time we get the cache, we will call the load method of CacheLoader to refresh the cache. For example, build a cache with a refresh rate of 10 minutes:
CacheBuilder.newBuilder() // Set the cache to refresh through the load method of CacheLoader 10 minutes after writing .refreshAfterWrite(10, TimeUnit.SECONDS) // jdk8 can use Duration later // .refreshAfterWrite(Duration.ofMinutes(10)) .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // Cache load logic ... } });
Explicit refresh
After the Cache is built, we can explicitly refresh and overwrite the Cache through some excuse methods provided by Cache, such as:
// Build a cache Cache<String, String> cache = CacheBuilder.newBuilder().build(); // Overwrite refresh with put cache.put("k1", "v1"); // Using the put method of Map to refresh the overlay cache.asMap().put("k1", "v1"); // Using the putAll method of Map to refresh batch coverage Map<String,String> needRefreshs = new HashMap<>(); needRefreshs.put("k1", "v1"); cache.asMap().putAll(needRefreshs); // Use the replace method of ConcurrentMap for overwrite refresh cache.asMap().replace("k1", "v1");
For LoadingCache, because it can automatically load the cache, there is no need for explicit incoming cache values when refreshing:
LoadingCache<String, String> loadingCache = CacheBuilder .newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { // Cache load logic return null; } }); // loadingCache does not need to explicitly pass in value when refreshing loadingCache.refresh("k1");
Original text: https://rumenz.com/rumenbiji/google-guava-java.html