Path: blob/master/src/java.base/share/classes/sun/security/util/Cache.java
41159 views
/*1* Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.util;2627import java.util.*;28import java.lang.ref.*;2930/**31* Abstract base class and factory for caches. A cache is a key-value mapping.32* It has properties that make it more suitable for caching than a Map.33*34* The factory methods can be used to obtain two different implementations.35* They have the following properties:36*37* . keys and values reside in memory38*39* . keys and values must be non-null40*41* . maximum size. Replacements are made in LRU order.42*43* . optional lifetime, specified in seconds.44*45* . safe for concurrent use by multiple threads46*47* . values are held by either standard references or via SoftReferences.48* SoftReferences have the advantage that they are automatically cleared49* by the garbage collector in response to memory demand. This makes it50* possible to simple set the maximum size to a very large value and let51* the GC automatically size the cache dynamically depending on the52* amount of available memory.53*54* However, note that because of the way SoftReferences are implemented in55* HotSpot at the moment, this may not work perfectly as it clears them fairly56* eagerly. Performance may be improved if the Java heap size is set to larger57* value using e.g. java -ms64M -mx128M foo.Test58*59* Cache sizing: the memory cache is implemented on top of a LinkedHashMap.60* In its current implementation, the number of buckets (NOT entries) in61* (Linked)HashMaps is always a power of two. It is recommended to set the62* maximum cache size to value that uses those buckets fully. For example,63* if a cache with somewhere between 500 and 1000 entries is desired, a64* maximum size of 750 would be a good choice: try 1024 buckets, with a65* load factor of 0.75f, the number of entries can be calculated as66* buckets / 4 * 3. As mentioned above, with a SoftReference cache, it is67* generally reasonable to set the size to a fairly large value.68*69* @author Andreas Sterbenz70*/71public abstract class Cache<K,V> {7273protected Cache() {74// empty75}7677/**78* Return the number of currently valid entries in the cache.79*/80public abstract int size();8182/**83* Remove all entries from the cache.84*/85public abstract void clear();8687/**88* Add an entry to the cache.89*/90public abstract void put(K key, V value);9192/**93* Get a value from the cache.94*/95public abstract V get(Object key);9697/**98* Remove an entry from the cache.99*/100public abstract void remove(Object key);101102/**103* Pull an entry from the cache.104*/105public abstract V pull(Object key);106107/**108* Set the maximum size.109*/110public abstract void setCapacity(int size);111112/**113* Set the timeout(in seconds).114*/115public abstract void setTimeout(int timeout);116117/**118* accept a visitor119*/120public abstract void accept(CacheVisitor<K,V> visitor);121122/**123* Return a new memory cache with the specified maximum size, unlimited124* lifetime for entries, with the values held by SoftReferences.125*/126public static <K,V> Cache<K,V> newSoftMemoryCache(int size) {127return new MemoryCache<>(true, size);128}129130/**131* Return a new memory cache with the specified maximum size, the132* specified maximum lifetime (in seconds), with the values held133* by SoftReferences.134*/135public static <K,V> Cache<K,V> newSoftMemoryCache(int size, int timeout) {136return new MemoryCache<>(true, size, timeout);137}138139/**140* Return a new memory cache with the specified maximum size, unlimited141* lifetime for entries, with the values held by standard references.142*/143public static <K,V> Cache<K,V> newHardMemoryCache(int size) {144return new MemoryCache<>(false, size);145}146147/**148* Return a dummy cache that does nothing.149*/150@SuppressWarnings("unchecked")151public static <K,V> Cache<K,V> newNullCache() {152return (Cache<K,V>) NullCache.INSTANCE;153}154155/**156* Return a new memory cache with the specified maximum size, the157* specified maximum lifetime (in seconds), with the values held158* by standard references.159*/160public static <K,V> Cache<K,V> newHardMemoryCache(int size, int timeout) {161return new MemoryCache<>(false, size, timeout);162}163164/**165* Utility class that wraps a byte array and implements the equals()166* and hashCode() contract in a way suitable for Maps and caches.167*/168public static class EqualByteArray {169170private final byte[] b;171private int hash;172173public EqualByteArray(byte[] b) {174this.b = b;175}176177public int hashCode() {178int h = hash;179if (h == 0 && b.length > 0) {180hash = h = Arrays.hashCode(b);181}182return h;183}184185public boolean equals(Object obj) {186if (this == obj) {187return true;188}189if (obj instanceof EqualByteArray == false) {190return false;191}192EqualByteArray other = (EqualByteArray)obj;193return Arrays.equals(this.b, other.b);194}195}196197public interface CacheVisitor<K,V> {198public void visit(Map<K,V> map);199}200201}202203class NullCache<K,V> extends Cache<K,V> {204205static final Cache<Object,Object> INSTANCE = new NullCache<>();206207private NullCache() {208// empty209}210211public int size() {212return 0;213}214215public void clear() {216// empty217}218219public void put(K key, V value) {220// empty221}222223public V get(Object key) {224return null;225}226227public void remove(Object key) {228// empty229}230231public V pull(Object key) {232return null;233}234235public void setCapacity(int size) {236// empty237}238239public void setTimeout(int timeout) {240// empty241}242243public void accept(CacheVisitor<K,V> visitor) {244// empty245}246247}248249class MemoryCache<K,V> extends Cache<K,V> {250251private static final float LOAD_FACTOR = 0.75f;252253// XXXX254private static final boolean DEBUG = false;255256private final Map<K, CacheEntry<K,V>> cacheMap;257private int maxSize;258private long lifetime;259private long nextExpirationTime = Long.MAX_VALUE;260261// ReferenceQueue is of type V instead of Cache<K,V>262// to allow SoftCacheEntry to extend SoftReference<V>263private final ReferenceQueue<V> queue;264265public MemoryCache(boolean soft, int maxSize) {266this(soft, maxSize, 0);267}268269public MemoryCache(boolean soft, int maxSize, int lifetime) {270this.maxSize = maxSize;271this.lifetime = lifetime * 1000;272if (soft)273this.queue = new ReferenceQueue<>();274else275this.queue = null;276277cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true);278}279280/**281* Empty the reference queue and remove all corresponding entries282* from the cache.283*284* This method should be called at the beginning of each public285* method.286*/287private void emptyQueue() {288if (queue == null) {289return;290}291int startSize = cacheMap.size();292while (true) {293@SuppressWarnings("unchecked")294CacheEntry<K,V> entry = (CacheEntry<K,V>)queue.poll();295if (entry == null) {296break;297}298K key = entry.getKey();299if (key == null) {300// key is null, entry has already been removed301continue;302}303CacheEntry<K,V> currentEntry = cacheMap.remove(key);304// check if the entry in the map corresponds to the expired305// entry. If not, readd the entry306if ((currentEntry != null) && (entry != currentEntry)) {307cacheMap.put(key, currentEntry);308}309}310if (DEBUG) {311int endSize = cacheMap.size();312if (startSize != endSize) {313System.out.println("*** Expunged " + (startSize - endSize)314+ " entries, " + endSize + " entries left");315}316}317}318319/**320* Scan all entries and remove all expired ones.321*/322private void expungeExpiredEntries() {323emptyQueue();324if (lifetime == 0) {325return;326}327int cnt = 0;328long time = System.currentTimeMillis();329if (nextExpirationTime > time) {330return;331}332nextExpirationTime = Long.MAX_VALUE;333for (Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();334t.hasNext(); ) {335CacheEntry<K,V> entry = t.next();336if (entry.isValid(time) == false) {337t.remove();338cnt++;339} else if (nextExpirationTime > entry.getExpirationTime()) {340nextExpirationTime = entry.getExpirationTime();341}342}343if (DEBUG) {344if (cnt != 0) {345System.out.println("Removed " + cnt346+ " expired entries, remaining " + cacheMap.size());347}348}349}350351public synchronized int size() {352expungeExpiredEntries();353return cacheMap.size();354}355356public synchronized void clear() {357if (queue != null) {358// if this is a SoftReference cache, first invalidate() all359// entries so that GC does not have to enqueue them360for (CacheEntry<K,V> entry : cacheMap.values()) {361entry.invalidate();362}363while (queue.poll() != null) {364// empty365}366}367cacheMap.clear();368}369370public synchronized void put(K key, V value) {371emptyQueue();372long expirationTime = (lifetime == 0) ? 0 :373System.currentTimeMillis() + lifetime;374if (expirationTime < nextExpirationTime) {375nextExpirationTime = expirationTime;376}377CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);378CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);379if (oldEntry != null) {380oldEntry.invalidate();381return;382}383if (maxSize > 0 && cacheMap.size() > maxSize) {384expungeExpiredEntries();385if (cacheMap.size() > maxSize) { // still too large?386Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();387CacheEntry<K,V> lruEntry = t.next();388if (DEBUG) {389System.out.println("** Overflow removal "390+ lruEntry.getKey() + " | " + lruEntry.getValue());391}392t.remove();393lruEntry.invalidate();394}395}396}397398public synchronized V get(Object key) {399emptyQueue();400CacheEntry<K,V> entry = cacheMap.get(key);401if (entry == null) {402return null;403}404long time = (lifetime == 0) ? 0 : System.currentTimeMillis();405if (entry.isValid(time) == false) {406if (DEBUG) {407System.out.println("Ignoring expired entry");408}409cacheMap.remove(key);410return null;411}412return entry.getValue();413}414415public synchronized void remove(Object key) {416emptyQueue();417CacheEntry<K,V> entry = cacheMap.remove(key);418if (entry != null) {419entry.invalidate();420}421}422423public synchronized V pull(Object key) {424emptyQueue();425CacheEntry<K,V> entry = cacheMap.remove(key);426if (entry == null) {427return null;428}429430long time = (lifetime == 0) ? 0 : System.currentTimeMillis();431if (entry.isValid(time)) {432V value = entry.getValue();433entry.invalidate();434return value;435} else {436if (DEBUG) {437System.out.println("Ignoring expired entry");438}439return null;440}441}442443public synchronized void setCapacity(int size) {444expungeExpiredEntries();445if (size > 0 && cacheMap.size() > size) {446Iterator<CacheEntry<K,V>> t = cacheMap.values().iterator();447for (int i = cacheMap.size() - size; i > 0; i--) {448CacheEntry<K,V> lruEntry = t.next();449if (DEBUG) {450System.out.println("** capacity reset removal "451+ lruEntry.getKey() + " | " + lruEntry.getValue());452}453t.remove();454lruEntry.invalidate();455}456}457458maxSize = size > 0 ? size : 0;459460if (DEBUG) {461System.out.println("** capacity reset to " + size);462}463}464465public synchronized void setTimeout(int timeout) {466emptyQueue();467lifetime = timeout > 0 ? timeout * 1000L : 0L;468469if (DEBUG) {470System.out.println("** lifetime reset to " + timeout);471}472}473474// it is a heavyweight method.475public synchronized void accept(CacheVisitor<K,V> visitor) {476expungeExpiredEntries();477Map<K,V> cached = getCachedEntries();478479visitor.visit(cached);480}481482private Map<K,V> getCachedEntries() {483Map<K,V> kvmap = new HashMap<>(cacheMap.size());484485for (CacheEntry<K,V> entry : cacheMap.values()) {486kvmap.put(entry.getKey(), entry.getValue());487}488489return kvmap;490}491492protected CacheEntry<K,V> newEntry(K key, V value,493long expirationTime, ReferenceQueue<V> queue) {494if (queue != null) {495return new SoftCacheEntry<>(key, value, expirationTime, queue);496} else {497return new HardCacheEntry<>(key, value, expirationTime);498}499}500501private static interface CacheEntry<K,V> {502503boolean isValid(long currentTime);504505void invalidate();506507K getKey();508509V getValue();510511long getExpirationTime();512}513514private static class HardCacheEntry<K,V> implements CacheEntry<K,V> {515516private K key;517private V value;518private long expirationTime;519520HardCacheEntry(K key, V value, long expirationTime) {521this.key = key;522this.value = value;523this.expirationTime = expirationTime;524}525526public K getKey() {527return key;528}529530public V getValue() {531return value;532}533534public long getExpirationTime() {535return expirationTime;536}537538public boolean isValid(long currentTime) {539boolean valid = (currentTime <= expirationTime);540if (valid == false) {541invalidate();542}543return valid;544}545546public void invalidate() {547key = null;548value = null;549expirationTime = -1;550}551}552553private static class SoftCacheEntry<K,V>554extends SoftReference<V>555implements CacheEntry<K,V> {556557private K key;558private long expirationTime;559560SoftCacheEntry(K key, V value, long expirationTime,561ReferenceQueue<V> queue) {562super(value, queue);563this.key = key;564this.expirationTime = expirationTime;565}566567public K getKey() {568return key;569}570571public V getValue() {572return get();573}574575public long getExpirationTime() {576return expirationTime;577}578579public boolean isValid(long currentTime) {580boolean valid = (currentTime <= expirationTime) && (get() != null);581if (valid == false) {582invalidate();583}584return valid;585}586587public void invalidate() {588clear();589key = null;590expirationTime = -1;591}592}593594}595596597