Google,Guava local efficient cache

Posted by wgh on Sat, 19 Oct 2019 08:11:04 +0200

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

Strong references are the most commonly used references. If an object has a strong reference, the garbage collector will never recycle it.

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

Topics: Java Google Apache xml